遇见狂神说的Java多线程教学笔记
Thread
class
Thread
class不建议使用:OOP单继承局限性
public class TestThread extends Thread{ @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("线程运行"+i); } } public static void main(String[] args) { TestThread thread = new TestThread(); thread.start(); for (int i = 0; i < 1000; i++) { System.out.println("主线程运行"+i); } } }
Runnable
接口
MyRunnable
类实现Runnable
接口实现run()
方法,编写线程执行体
new Thread(myRunnable).start()
推荐使用:避免单继承局限性、方便同一个对象被多个线程使用
public class TestThread4 implements Runnable{ private int ticketNums = 10; @Override public void run() { while (true) { if(ticketNums < 0) { break; } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" got "+ticketNums--+"ticket"); } } public static void main(String[] args) { TestThread4 ticket = new TestThread4(); new Thread(ticket,"ming").start(); new Thread(ticket,"lao").start(); new Thread(ticket,"huang").start(); //涉及数据紊乱问题,通过加锁解决 } }
Callable
接口
Callable
接口,需要返回值类型call
方法,需要抛出异常ExecutorService ser = Executors.newFixedThreadPool(1);//线程池里的线程数为1
Future<Boolean> result1 = ser.submit(t1);//t1是目标对象
boolean r1 = result1.get();
ser.shutdownNow();
可以定义返回值,抛出异常
public class TestCallable implements Callable<Boolean> { private String name; public TestCallable(String name) { this.name = name; } @Override public Boolean call() throws Exception { System.out.println(name); return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { TestCallable t1 = new TestCallable("ming"); TestCallable t2 = new TestCallable("hong"); ExecutorService ser = Executors.newFixedThreadPool(2); Future<Boolean> result1 = ser.submit(t1); Future<Boolean> result2 = ser.submit(t2); boolean rs1 = result1.get(); boolean rs2 = result2.get(); System.out.println(rs1); System.out.println(rs2); ser.shutdownNow(); } }
不使用已废弃的stop()、destroy()
方法。
使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
public class TestStop implements Runnable{ private boolean flag = true; @Override public void run(){ while(flag){ //自己的代码 } } public void stop() { this.flag = false; } }
Thread.sleep(long millis)
//实现倒计时或时钟显示 public class TestSleep { public static void main(String[] args) throws InterruptedException { // TestSleep.tenDown(); //时钟显示 Date startTime = new Date(System.currentTimeMillis()); while (true){ try { Thread.sleep(1000); System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime)); startTime = new Date(System.currentTimeMillis()); }catch (Exception e){ e.printStackTrace(); } } } //倒计时 public static void tenDown() throws InterruptedException { int num = 10; while (true){ Thread.sleep(1000); System.out.println(num--); if(num<=0){ break; } } } }
Thread.yield();
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行态转为就绪态
让CPU重新调度,礼让不一定成功(可能还会调用暂停的那个线程)
public class TestYield { public static void main(String[] args) { MyYield myYield = new MyYield(); new Thread(myYield,"a").start(); new Thread(myYield,"b").start(); } } class MyYield implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+" start"); Thread.yield(); System.out.println(Thread.currentThread().getName()+" stop"); } }
join在线程里面意味着“插队”,哪个线程调用join代表哪个线程插队先执行——但是插谁的队是有讲究了,不是说你可以插到队头去做第一个吃螃蟹的人,而是插到在当前运行线程的前面,比如系统目前运行线程A,在线程A里面调用了线程B.join()
方法,则接下来线程B会抢先在线程A面前执行,等到线程B全部执行完后才继续执行线程A
public class TestJoin implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("vip"+i); } } public static void main(String[] args) throws InterruptedException { TestJoin testJoin = new TestJoin(); Thread thread = new Thread(testJoin); thread.start(); for (int i = 0; i < 500; i++) { if(i==200){ thread.join(); } System.out.println("main"+i); } } }
Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程的优先级用数字表示,范围从1-10。
getPriority()
获取优先级
setPriority(int p)
设置优先级
优先级的设置应在start()
之前;
setDaemon(true)
注:在start()
之前设置;如:后台记录操作日志、监控内存、垃圾回收等
解决多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用。
synchronized
关键字synchronized
是Java中解决并发问题的一种最常用最简单的方法 ,它可以确保线程互斥的访问同步代码。
synchronized
应用:
public class TestSynchronized { SyncObject syncObject; //既是对象锁也是方法锁 public synchronized void method1(){} // 对象锁:形式2(代码块形式) public void method2(){ synchronized(syncObject){ // 访问或修改被锁保护的共享状态 } } //类锁:形式1 :锁静态方法 public static synchronized void method3(){} // 类锁:形式2 :锁静态代码块 public void method4() { synchronized (TestSynchronized.class) {} } }
注:
synchronized
修饰方法时存在缺陷:若修饰1个大的方法,将会大大影响效率
若使用synchronized
关键字修饰 线程类的run()
,由于run()
在线程的整个生命期内一直在运行,因此将导致它对本类任何synchronized
方法的调用都永远不会成功
解决方案:使用 synchronized
关键字声明代码块,该解决方案灵活性高:可针对任意代码块 & 任意指定上锁的对象
更多有关synchronized的知识可通过该篇博文学习https://blog.csdn.net/carson_ho/article/details/82992269
Lock是一个类,通过这个类可以实现同步访问。
Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
ReentrantLock
是唯一实现了Lock接口的类
class A{ private final Lock lokc = new ReenTrantLock(); public void method(){ lock.lock(); try{ }catch(Exception e){ }finally{ lock.unlock(); } } }
更多https://www.cnblogs.com/dolphin0520/p/3923167.html
ReentrantReadWriteLock
有两把锁,读锁和写锁
class RWLDemo{ private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock writeLock = rwl.writeLock(); private final Lock readLock = rwl.readLock(); public void m1() { //上读锁,其他线程只能读不能写 readLock.unlock(); try { System.out.println("get read lock"); } catch (Exception e) { } finally { readLock.unlock(); } //上写锁,其他线程既不能读也不能写 writeLock.lock(); try { System.out.println("get write lock"); }catch (Exception e){ }finally { writeLock.unlock(); } //先读再写的情况,请先释放读锁,再上写锁,ReentrantReadWriteLock不支持读锁升级为写锁 readLock.lock(); try { System.out.println("get read lock"); }catch (Exception e){ }finally { readLock.unlock(); } writeLock.lock(); try { System.out.println("get write lock"); }catch (Exception e){ }finally { writeLock.unlock(); } /** 先写再读的情况,可以在上写锁的情况,再上读锁,解锁的时候先解读锁再解写锁(ReentrantReadWriteLock支持锁降级,因此这段代码不会造成死锁) **/ writeLock.lock(); try { System.out.println("get write lock"); readLock.lock(); try { System.out.println("get read lock"); }catch (Exception e){ }finally { readLock.unlock(); } }catch (Exception e){ }finally { writeLock.unlock(); } } }
ReentrantReadWriteLock
的使用的可以--->https://blog.csdn.net/weixin_33779515/article/details/92575574Java 提供了几个方法解决线程之间的通信问题:
wait()
表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁wait(long timeout)
指定等待的毫秒数notify()
唤醒一个处于等待状态的线程notifyAll()
唤醒同一个对象上所有调用wait()
方法的线程,优先级高的线程优先调度均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常。
深入实践可以去了解同步问题:生产者-消费者问题、读者-写作问题、哲学家进餐问题、吸烟者问题等
生产者消费者问题(管道法)
public class TestPC { public static void main(String[] args) { SynContainer container = new SynContainer(); new Productor(container).start(); new Consumer(container).start(); } } //生产者 class Productor extends Thread{ SynContainer container; public Productor(SynContainer container) { this.container = container; } //生产 @Override public void run() { for (int i = 0; i < 100; i++) { container.push(new Procduct(i)); System.out.println("生产了"+i+"件产品"); } } } //消费者 class Consumer extends Thread{ SynContainer container; public Consumer(SynContainer container) { this.container = container; } //消费 @Override public void run() { for (int i = 0; i < 100; i++) { container.pop(); } } } //产品 class Procduct{ int id; public Procduct(int id) { this.id = id; } } //缓存区 class SynContainer{ //容器大小 Procduct[] procducts = new Procduct[10]; int count = 0; //生产者放入产品 public synchronized void push(Procduct procduct){ //如果容器满了,等待消费者消费 if (count == procducts.length){ //通知消费者消费,生产等待 try { System.out.println("生产等待"); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //没满就需要丢入产品 procducts[count++] = procduct; this.notifyAll(); //可以通知消费者消费了 } //消费者消费产品 public synchronized Procduct pop(){ //判断能否消费 if (count == 0){ //等待生产者生产,消费者等待 try { System.out.println("消费者等待"); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //消费 count--; Procduct procduct = procducts[count]; System.out.println("消费了"+procduct.id+"件产品"); //通知生产者生产 this.notifyAll(); return procduct; } }
生产者-消费者问题(信号灯法)
public class TestPC2 { public static void main(String[] args) { TV tv = new TV(); new Player(tv).start(); new Watcher(tv).start(); } } //生产者--演员 class Player extends Thread{ TV tv; public Player(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { if (i%2==0){ this.tv.play("快乐大本营"); }else { this.tv.play("天天向上"); } } } } //消费者--观众 class Watcher extends Thread{ TV tv; public Watcher(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { tv.watch(); } } } //产品--节目 class TV{ //演员表演,观众等待 //观众观看,演员等待 String voice; //表演的节目 boolean flag = true; //表演 public synchronized void play(String voice){ if (!flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Player played"+voice); //通知观众观看 this.voice = voice; this.notifyAll(); this.flag = !this.flag; } //观看 public synchronized void watch(){ if (flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("watched"+voice); this.notifyAll(); this.flag = !this.flag; } }
提前创建好多个线程,放入线程池中,使用时直接获取,使用完后放回池中。可以避免频繁地创建销毁、实现重复利用,减少对系统性能的影响。
好处:
corePoolSize
核心池的大小maixmunPoolSize
最大线程数keepAliveTime
线程无任务时最多保持多长时间后会终止public class TestTP { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(10); service.execute(new MyRunnable()); service.execute(new MyRunnable()); service.execute(new MyRunnable()); service.shutdownNow(); } } class MyRunnable implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
阿里提供的Java规范明确表明了线程池不允许使用 Executors
去创建,而是通过 ThreadPoolExecutor
的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors
返回的线程池对象的弊端如下: 1. FixedThreadPool
和 SingleThreadPool
: 允许的请求队列长度为 Integer.MAX_VALUE
,可能会堆积大量的请求,从而导致 OOM。 2.CachedThreadPool
和 ScheduledThreadPool
: 允许的创建线程数量为 Integer.MAX_VALUE
,可能会创建大量的线程,从而导致 OOM。
自定义线程池:
public class TestThreadPoolExecutor{ public static void main(String[] args) { //等待队列 BlockingQueue<Runnable> bq = new ArrayBlockingQueue<Runnable>(10); //创建一个带名字的线程池生产工厂 NamingThreadFactory factory = new NamingThreadFactory("--name--"); //3个核心线程,6个最大线程,1000毫秒线程空闲存活时间 ThreadPoolExecutor tpe = new ThreadPoolExecutor(3,6,1000, TimeUnit.MILLISECONDS,bq,factory); tpe.execute(new MyRunnable()); tpe.execute(new MyRunnable()); tpe.execute(new MyRunnable()); tpe.shutdown(); } }
自定义线程工厂
public final class NamingThreadFactory implements ThreadFactory { private final AtomicInteger threadNum = new AtomicInteger(); private final String name; /** * 创建一个带名字的线程池生产工厂 */ public NamingThreadFactory(String name) { this.name = name; } @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setName(name + threadNum.incrementAndGet()); return t; } }
更多了解https://juejin.cn/post/6882992964709122055