java基础笔记06 - 多线程
六 多线程
1.基本概念
? 程序:一组指令的集合,一段静态的代码
? 进程:程序的一次执行过程,或者正在运行的一个程序,是一个动态的工作恒,有自身的产生,存在和消亡的过程--------生命周期
? 程序是静态的的,晋城市动态的
? 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径
? 线程作为调度的最小单位,每个线程拥有独立的运行栈(VM Stack)和程序计数器(pc) 线程的切换开销小
? 多个线程共享进程的资源(方法区和堆),所以访问的是相同的变量和对象
? 一个java.exe,至少三个线程:main线程, gc垃圾回收线程,异常处理线程
2. 线程的创建使用
2.1 方式1
2.1.1 创建
? -创建一个继承于Thread类的子类
? -重写Thread类的run方法
? -创建子类对象
? -调用子类对象的start()方法 ,只能start一次
import java.lang.Thread;
public Test extends Thread{
@override
public void run(){
//将此线程执行的操作声明在run()中
for(int i = 0; i<100;i++){
if(i %2==0){
sout(i);
}
}
}
}
main(){
Test t= new Test();
t.start(); //start会启动线程,然后调用run方法
}
2.1.2省略方式(匿名对象)
public static void main(String[] args){
new Thread(){
@override
public void run(){
.....
}
}.start();
}
2.1.3创建三个窗口卖100张票
注意使用static来解决线程间共享资源的问题,但是目前的代码还没做线程安全处理
public class Ly extends Thread {
private static final int ticket = 100;
private static int num=0;
public Ly(String name) {
super(name);
}
public void run() {
while (true) {
if (num < ticket) {
num++;
System.out.println(getName() + ":买票,票号为:" + num);
} else {
break;
}
}
}
public static void main(String[] args) {
new Ly("窗口1").start();
new Ly("窗口2").start();
new Ly("窗口3").start();
}
}
2.3 Thread类的常见方法
? void start():启动当前线程, 调用run方法
? void run():需要重写,将线程需要执行的操作,卸载run里面
? String getName();
? void setName();//在start方法之前设置
? static Thread currentThread();返回当前代码执行时的线程
? public Thread(String s);//Thread类的构造器,可以直接起名,构造方法挺多的,这个是线程名参数的
? yield( );//让步,释放当前线程的cpu的执行(可能紧接着cpu又分给它了,没准的事)
? join(); // 把cpu切换到当前线程,直到其执行完才结束阻塞!!!
? stop();//强制结束,干掉
? sleep(millis);//单位毫秒,需要try-catch,阻塞当前线程
? isAlive();//判断当前线程还活着么
2.4线程调度
? 时间片,抢占式
? 同优先级的线程,先到先得,使用时间片策略
? 高优先级使用抢占式策略
2.4.1线程优先级
MAX_PRIORITY=10;
MIN_PRIORITY=1;
NORM_PRIORITY=5; //默认优先级
2.4.2获取和设置线程优先级
在start之前设置吧,最好
getPriority();
setPriority(int);
2.5 方式2
2.5.1创建
? -创建一个实现了Runnable接口的类
? -实现类去实现Runnable中的抽象方法run()
? -创建实现类的对象
? -将此对象作为参数传递到Thread类的构造方法中,创建Thread类的对象
? -通过Thread类的对象调用start()方法
public class Ly implements Runnable {
@Override
public void run() {
for (int i=0;i<100;i++){
if (i%2==0) {
System.out.println(i);
}
}
}
public static void main(String[] args) {
Thread th=new Thread(new Ly());
th.start();
}
}
? Q:调用的是Thread类里面的run,为啥跑的是Ly实现类的run
? A:Thread里面声明了一个Runnable类型的target成员
? 同时,Thread有一个构造器:Thread(Runnable target),传入target之后,会调用target的run
2.5.2创建三个窗口卖100张票
? 注意,只创建了一个Runnable实现类的对象,然后三个线程就共用一个资源了
? 一样,先忽略线程安全问题
public class Ly implements Runnable {
private int ticket = 10;
private int num = 0;
@Override
public void run() {
while (true) {
if (num < ticket) {
num++;
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + num);
} else {
break;
}
}
}
public static void main(String[] args) {
Ly ly = new Ly(); //注意,只有一个Runnable的实现类
Thread th1 = new Thread(ly);
th1.setName("窗口1");
th1.start();
Thread th2 = new Thread(ly);
th2.setName("窗口2");
th2.start();
Thread th3 = new Thread(ly);
th3.setName("窗口3");
th3.start();
}
}
2.6两种创建线程方式的对比
别问,问就是用Runnable方式
实际开发,线程类可能需要有继承关系,就不能继承Thread类了,为了避免多继承,提供了Runnable接口
同时,Runnable天然提供了线程资源共享功能;
3. 线程的生命周期
3.1 线程的几种状态
? NEW,新建 Thread state for a thread which has not yet started.
? RUNNABLE:运行 executing in the Java virtual machine but it may be waiting for other resources from the operating system such as
? BLOCKED:阻塞,在队列里等待cpu (原因:调用sleep了,join其他线程了,等待同步锁,wait(), suspend过时了,因为导致死锁 )
? 阻塞之后会回到WAITING就绪状态
? WAITING:调了start方法 等待cpu时间片,或者是yield的了
? TIMED_WAITING:
? TERMINATED:terminated死亡了(运行完了,stop了,异常了)
? 就直接记住 新建,就绪,运行,阻塞,死亡,五种就行
4.线程的同步
4.1最初的两种方式
4.1.1 方式1:同步代码块
把操作共享数据的代码程序需要同步的代码
注意:
? 1.不能包多了,也不能包少了,少了导致线程不安全,多了可能导致某个线程霸占资源
? 2. 对于某一个临界资源来说,锁只能放一把,而在第一种继承的方式创建多线程时,注意锁别声明多了,(继承方式天然生成多个类)
synchronized(同步监视器){ //俗称锁,任何一个类的对象都能充当锁,放只狗都行,但是!!只能是放一只狗,两只就不行
//需要被同步的代码
}
import javax.security.auth.kerberos.KerberosTicket;
public class Test implements Runnable {
private int ticket = 10;
Object obj = new Object();
public void run() {
while (true) {
synchronized (obj) { //加把锁 ,用this也行,或者Test.class(因为类也是对象)
//个人猜测,就是想放一个唯一的标识符,甚至怀疑他这里用的对象的hash值
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":买票,票号:" + ticket);
ticket--;
} else {
break;
}
}
}
}
public static void main(String[] args) {
Test test=new Test();
Thread th1=new Thread(test);
Thread th2=new Thread(test);
Thread th3=new Thread(test);
th1.start();
th2.start();
th3.start();
}
}
缺点:操作同步代码时,只能有一个线程参与,退化成单线程了,速度变慢了
4.1.2 方式2:同步方法
如果临界资源涉及到的代码正好完整的声明在了一个方法中,则将该方法声明为同步方法
其实还是有个同步监视器,只不过没有显式 的声明了
非静态的同步方法,锁是this
静态的同步方法,锁是类本身
实现接口的多继承方式:
import javax.security.auth.kerberos.KerberosTicket;
/**
* @author
* @create 2021-11-25 21:40
*/
public class Test implements Runnable {
private int ticket = 10;
Object obj = new Object();
public void run() {
while (true) {
sailticket();
}
}
private synchronized void sailticket(){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":买票,票号:" + ticket);
ticket--;
} else {
break;
}
}
public static void main(String[] args) {
Test test=new Test();
Thread th1=new Thread(test);
Thread th2=new Thread(test);
th1.start();
th2.start();
}
}
继承的多线程方式
同步方法要设置成静态的,.
别问,问就是不用
4.1.3 懒汉式设计模式的改写
使用同步机制,将单例模式中的懒汉式改写为线程安全的 (多个线程同时创建一个单例
import javax.security.auth.kerberos.KerberosTicket;
public class Test implements Runnable {
public Bank bank;
public void run(){
this.bank=Bank.getInstance();
System.out.println(bank);
}
public static void main(String[] args) {
Test test=new Test();
Thread th1=new Thread(test);
Thread th2=new Thread(test);
th1.start();
th2.start();
}
}
class Bank{
private Bank(){}
private static Bank instance=null; //instance算是一个临界资源
//方式1
public static Bank getInstance1(){
if (instance==null){
synchronzed(Bank.class){
if (instance==null){
instance=new Bank();
}
}
}
return instance;
}
//方式2
public static synchronzed Bank getInstance2(){
if (instance==null){
instance=new Bank();
}
return instance;
}
}
4.1.3死锁问题
俩匿名的线程类,一个继承实现,一个接口实现,这俩抢两个临界资源,造成死锁
public class Test {
public static void main(String[] args) {
StringBuffer s1=new StringBuffer();
StringBuffer s2=new StringBuffer();
new Thread(){
public void run(){
synchronized (s1){
try {
sleep(100); //sleep是为了方便演示死锁
} catch (InterruptedException e) {
e.printStackTrace();
}
s1.append("a");
s2.append("1");
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
s1.append("c");
s2.append("3");
synchronized (s1) {
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
4.2 方式3 :Lock锁
老规矩,继承方式实现的时候lock必须是static的,保证锁是唯一的
import java.util.concurrent.locks.ReentrantLock;
import static java.lang.Thread.sleep;
public class Test implements Runnable {
private int ticket = 10;
//step1:实例化一个Lock
private ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true) {
try{
//step2:调用lock
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号:" + ticket);
ticket--;
} else {
break;
}
}finally {
//step3:调用unlock
lock.unlock();
}
}
}
public static void main(String[] args) {
Test test = new Test();
Thread th1 = new Thread(test);
Thread th2 = new Thread(test);
th1.setName("窗口1");
th2.setName("窗口2");
th1.start();
th2.start();
}
}
举例存钱
import java.util.concurrent.locks.ReentrantLock;
class Account{
private int count=0;
private ReentrantLock lock=new ReentrantLock();
public void add(int amt) {
if(amt>0){
try{
lock.lock();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count+=amt;
System.out.println(Thread.currentThread().getName()+" 存钱成功,余额为:"+count);
}finally {
lock.unlock();
}
}
}
}
class AccountTest implements Runnable{
private Account account;
public AccountTest(Account account){
this.account=account;
}
@Override
public void run() {
for(int i=0;i<3;i++){
account.add(1000);
}
}
}
public class Test {
public static void main(String[] args) {
AccountTest test=new AccountTest(new Account());
Thread C1=new Thread(test);
Thread C2=new Thread(test);
C1.setName("wang");
C2.setName("zhang");
C1.start();
C2.start();
}
}
4.3 synchronized 和lock的差别
? Lock需要手动释放锁,
? synchronized自动释放,但是要在执行完{ }之后自动释放
? 基本老的用的都是synchronized,新的都建议用lock
? 别想了 synchronized多难写啊。。。。。
5. 线程的通信
5.1 线程交易阻塞、唤醒
举个例子:俩线程,交替打印123456.....
wait()方法:当前线程进入阻塞状态,并释放锁
notify()方法:唤醒wait的线程,如果有很多个,释放优先级最高的
notifyAll()方法:唤醒所有的阻塞线程
注意:
- 这三个方法必须使用在同步代码块或者同步方法中, lock方法的后面再讲
- 这三个方法调用者必须是锁,下例中是this,并且省略了
- 这三个方法不是定义在Thread或者Runnable中的,而是Object中
public class Test implements Runnable{
private int number=1;
@Override
public void run() {
while (true){
synchronized () {
notify();//唤醒方法
//notifyAll();//唤醒所有方法
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(number<=10){
System.out.println(Thread.currentThread().getName()+"打印:"+number);
number++;
try {
wait();//阻塞线程,wait方法是会释放锁的,sleep就不会
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
public static void main(String[] args) {
Test test=new Test();
Thread th1=new Thread(test);
Thread th2=new Thread(test);
th1.setName("线程1");
th2.setName("线程2");
th1.start();
th2.start();
}
}
5.2 经典题:生产者消费者问题
生产者Productor生产产品,交给店员Clerk,消费者Customer从店员处取产品,店员处产品存放有上限
注意:完全不加wait和notify,也没事,但是浪费线程资源,以生产者为例,即便满了
public class ProductorTest {
public static void main(String[] args) {
Clert clert=new Clert(); //这意味着锁是唯一的
Productor thp=new Productor(clert);
Thread p1=new Thread(thp);
p1.setName("生产者1");
Customer thc=new Customer(clert);
Thread c1=new Thread(thc);
Thread c2=new Thread(thc);
c1.setName("消费者1");
//c2.setName("消费者2");
p1.start();
c1.start();
// c2.start();
}
}
class Productor implements Runnable {
private Clert clert;
public Productor(Clert clert) {
this.clert = clert;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {e.printStackTrace();}
clert.product();
}
}
public void product(){
}
}
class Customer implements Runnable {
private Clert clert;
public Customer(Clert clert) {
this.clert = clert;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {e.printStackTrace();}
clert.custom();
}
}
}
class Clert{
private int number=0;
//生产产品
public synchronized void product() {
if(number<10){
number++;
notify();
System.out.println(Thread.currentThread().getName()+"++产品,库存:"+number);
}else {
try {
wait();
} catch (InterruptedException e) { e.printStackTrace();}
}
}
//消费产品
public synchronized void custom() {
if(number>0){
number--;
System.out.println(Thread.currentThread().getName()+"消耗产品,库存:"+number);
notify();
}else{
try {
wait();
} catch (InterruptedException e) {e.printStackTrace();}
}
}
}
6. JDK5.0 新增线程创建方式
6.1 实现Callable接口
6.1.1 对比Runnable,Callable
- Callable接口重写call方法
- 相比run()方法,call()方法有返回值
- 支持泛型得返回值
- run只能使用try catch,call方法可以抛出异常
- 需要借助FutureTask类 (他实现了Runnable和Callable接口)
6.1.2 实现
为啥这么麻烦了,因为要兼容老版本啊,那群傻逼死活就想用老系统有啥办法
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//step1:创建实现Callable接口得实现类
public class Test implements Callable {
//step2:将线程需要执行得操作放在call方法中,重写
@Override
public Integer call() throws Exception {
int sum=0;
for(int i=1;i<=10;i++){
sum+=i;
}
return sum;
}
public static void main(String[] args) {
//step3:创建Callable实现类的对象
Test test=new Test();
//step4:借助FutureTask类,将Callable对象传入其中
FutureTask task =new FutureTask(test);
//step5 : 类似于Runnable方法,将FutureTask的对象传递给Thread类,再start
new Thread(task).start();
try {
//step6:使用FutureTask类对象得方法get获取线程run的返回值
Object value = task.get(); //get方法得返回值就是Callable对象得call方法得返回值
//如果对call方法得返回值不感兴趣,则不调用get方法也行
System.out.println(value);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
6.2 使用线程池 (真正用的)
死麻烦死麻烦的,谁家想弄个线程还得声明三步变量啊(Callable一个,FutureTask一个,Thread一个)
实际开发都用线程池,是不是一瞬间觉得前面全白学了 ????
6.2.1 概念
提前创建好多个线程,放进线程池中,用的时候直接捞,用完了再扔回去,避免频繁的创建销毁
优点:
-
提高响应速度,因为不用创建了
-
降低了资源消耗,因为重复利用线程
-
便于线程管理
corePoolSize: //核心池得大小 maximumPoolSize://最大线程数 keepAliveTime://线程再没有任务时最多保持多长时间就终止
6.2.2 使用
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
//设置线程池得属性,ExecutorService是个接口,所以不能直接用service设置
ThreadPoolExecutor se= (ThreadPoolExecutor)service;
se.setCorePoolSize(15);
service.execute(new NumberThread1());//适合使用于Runnable
service.submit(new NumberThread2());//适合使用于Callable
service.shutdown(); //不加线程不结束
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i <10; i++) {
if (i%2==0)
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
class NumberThread2 implements Callable {
@Override
public Object call() {
for (int i = 0; i <10; i++) {
if (i%2==1)
System.out.println(Thread.currentThread().getName()+":"+i);
}
return null;
}
}