继承Thread类
首先要重写run方法,然后在使用的时候需要调用start方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class TestThread1 extends Thread { @Override public void run () { for (int i=0 ;i<20 ;i++){ System.out.println("1" ); } } public static void main (String[] args) { TestThread1 testThread1 = new TestThread1(); testThread1.start(); for (int i=0 ;i<20 ;i++){ System.out.println("2" ); } } }
文件下载的代码出问题了,原因尚不知道,先贴一下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import java.io.*;import java.net.URL;public class TestThread2 extends Thread { private final String url; private final String name; public TestThread2 (String url, String name) { this .url = url; this .name = name; } @Override public void run () { super .run(); WebDownloader webDownloader = new WebDownloader(); try { webDownloader.downloader(url, name); System.out.println("Succeed to download:" +name); } catch (IOException e) { e.printStackTrace(); System.out.println("IO exception happen when you try to download the files" ); } } public static void main (String[] args) { TestThread2 t1 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594993558795&di=e299a32a05127a6511851d8c3b431c5a&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20180321%2Fc315358a45e04f7cb072e827a40e4379.jpeg" , "1.jpeg" ); TestThread2 t2 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1602842887124&di=fde2e6863c115993abf1822605cbff0a&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F14%2F75%2F01300000164186121366756803686.jpg" , "2.jpg" ); TestThread2 t3 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1602842887124&di=a038d5c4dd0accb9d2b694634db8596d&imgtype=0&src=http%3A%2F%2Fa0.att.hudong.com%2F70%2F91%2F01300000261284122542917592865.jpg" , "3.jpg" ); t1.start(); t2.start(); t3.start(); } } class WebDownloader { public void downloader (String url, String name) throws IOException { URL downurl = new URL(url); InputStream in = new BufferedInputStream(downurl.openStream()); byte [] buf = new byte [1024 ]; int n = 0 ; FileOutputStream fos = new FileOutputStream(name); while (-1 !=(n=in.read(buf))){ fos.write(buf,0 ,n); } fos.close(); } }
实现Runnable接口
定义MyRunnable类实现Runnable接口
实现run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class TestRunnable implements Runnable { @Override public void run () { for (int i=0 ;i<20 ;i++){ System.out.println("1" ); } } public static void main (String[] args) { TestRunnable testRunnable = new TestRunnable(); Thread thread = new Thread(testRunnable); thread.start(); } }
因为java是单继承,所以推荐使用Runnable接口,避免OOP单继承局限性,方便同一个对象被多个线程使用。
lambda表达式
函数式接口:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public class TestLambda { static class ILike2 implements Like { @Override public void lambda () { System.out.println("I like lambda2" ); } } public static void main (String[] args) { ILike1 iLike1 = new ILike1(); iLike1.lambda(); ILike2 iLike2 = new ILike2(); iLike2.lambda(); class ILike3 implements Like { @Override public void lambda () { System.out.println("I like lambda3" ); } } ILike3 iLike3 = new ILike3(); iLike3.lambda(); Like iLike4 = new Like() { @Override public void lambda () { System.out.println("I like lambda4" ); } }; iLike4.lambda(); Like iLike5 = ()->{ System.out.println("I like lambda5" ); }; iLike5.lambda(); } } interface Like { void lambda () ; } class ILike1 implements Like { @Override public void lambda () { System.out.println("I like lambda1" ); } }
线程的停止
建议在线程中自己设置一个标志位,以及自定义的stop方法来设置这个标志位。
?这里i
声明的位置不同会导致不同的输出结果,假如是方法中的局部变量则刚好会在main中循环到900之后停止,但是作为属性出现时则会有不同的结果。假如是使用属性中的i
并且在使用的时候使用this.i
去引用那么和直接使用方法中的i
是一样的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class TestStop implements Runnable { private boolean flag = true ; @Override public void run () { int i=0 ; while (flag){ System.out.println("thread print:" + i++); } } public void stop () { this .flag = false ; } public static void main (String[] args) { TestStop testStop = new TestStop(); Thread thread = new Thread(testStop); thread.start(); for (int i=0 ;i<1000 ;i++){ if (i == 900 ){ testStop.stop(); } System.out.println("main print:" +i); } } }
线程休眠
sleep方法中的参数指定当前线程阻塞的毫秒数,sleep存在异常InterruptedException,时间结束后将会进入就绪状态,sleep可以模拟网络延时(可以放大问题)以及倒计时。sleep不会释放锁。
线程礼让yield
可以让当前正在执行的线程暂停,但是不阻塞,线程会从运行状态转为就绪状态,之后cpu会重新调度,不一定会成功。
1 2 3 4 5 6 7 8 9 10 11 12 public class TestYield { public static void main (String[] args) { Runnable testYield = () -> { System.out.println(Thread.currentThread()+"start" ); Thread.yield(); System.out.println(Thread.currentThread()+"stop" ); }; new Thread(testYield, "a" ).start(); new Thread(testYield, "b" ).start(); } }
线程强制执行join
可以想象成插队
观测线程状态
死亡之后的线程不能再次启动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class TestState { public static void main (String[] args) { Thread thread = new Thread(() -> { for (int i = 0 ; i < 5 ; i++) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Thread ends" ); }); Thread.State state = thread.getState(); System.out.println(state); thread.start(); Thread.State tmp_state = state; state = thread.getState(); System.out.println(state); while (state != Thread.State.TERMINATED){ if (tmp_state != state) System.out.println(state); tmp_state = state; state = thread.getState(); } } }
线程优先级
线程的优先级低只是被cpu调度的可能性比较低,并不是它不会被cpu调度
1 2 thread.getPriority(); thread.setPriority(5 );
守护线程(daemon)
线程分为用户线程 和守护线程 。
虚拟机必须确保用户线程执行完毕,但是不需要等待守护线程执行完毕,守护线程有 后台记录操作日志,监控内存,垃圾回收等
1 2 3 4 5 6 Thread thread = new Thread(()->{ while (true ){ System.out.println("This is Daemon thread" ); } }); thread.setDaemon(true );
此时程序能够正常的结束,因为该具有死循环的线程被设置为了守护线程。
线程安全
使用 队列+锁 的方式来保证线程的安全性。
同步方法与同步块
synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦开始执行就会独占该锁,知道该方法返回才会释放锁,后面被阻塞的线程才能获得这个锁,并继续执行。
不安全的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import java.util.ArrayList;public class TestSynchronized { public static void main (String[] args) { ArrayList<String> list = new ArrayList<String>(); for (int i = 0 ; i < 10000 ; i++) { new Thread(()-> list.add(Thread.currentThread().getName())).start(); } try { Thread.sleep(3000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
使用synchronized块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import javax.swing.plaf.TableHeaderUI;import java.util.ArrayList;public class TestSynchronized { public static void main (String[] args) { ArrayList<String> list = new ArrayList<String>(); for (int i = 0 ; i < 10000 ; i++) { new Thread(()-> { synchronized (list) { list.add(Thread.currentThread().getName()); } }).start(); } try { Thread.sleep(3000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
在方法中直接加上synchronized就是相当于synchronized(this){...}
我们需要上锁的内容应该是公共资源。
使用CopyOnWriteArrayList来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.util.concurrent.CopyOnWriteArrayList;public class TestSynchronized { public static void main (String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); for (int i = 0 ; i < 10000 ; i++) { new Thread(()->list.add(Thread.currentThread().getName())).start(); } try { Thread.sleep(3000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
使用ReentrantLock来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import java.util.ArrayList;import java.util.concurrent.locks.ReentrantLock;public class TestSynchronized { public static void main (String[] args) { ArrayList<String> list = new ArrayList<String>(); final ReentrantLock lock = new ReentrantLock(); for (int i = 0 ; i < 10000 ; i++) { new Thread(()-> { try { lock.lock(); list.add(Thread.currentThread().getName());} finally { lock.unlock(); } }).start(); } try { Thread.sleep(3000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
这里需要注意lock的作用域
线程通信
wait()
可以让线程一直等待,直到其他线程通知,它和sleep不同,它会释放锁。
notify()
可以唤醒一个处于等待状态的线程。
下面会使用生产者、消费者模型来演示
管程法
在生产者和消费者之间增加了一个缓冲区。生产者会将生产的东西放入缓冲区,同时消费者也会在这个缓冲区中拿。
缓存一致性
缓存一致性协议。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 public class TestPC { public static void main (String[] args) { Monitor monitor = new Monitor(); Producer producer = new Producer(monitor); Consumer consumer = new Consumer(monitor); producer.start(); consumer.start(); } } class Goods { final int id; public Goods (int id) { this .id = id; } } class Producer extends Thread { Monitor monitor; int i = 1 ; public Producer (Monitor monitor) { this .monitor = monitor; } @Override public void run () { super .run(); for (int j = 0 ; j < 100 ; j++) { monitor.push(new Goods(i)); System.out.println("Producer:" +i++); } } } class Consumer extends Thread { Monitor monitor; public Consumer (Monitor monitor) { this .monitor = monitor; } @Override public void run () { super .run(); for (int i = 0 ; i < 100 ; i++) { System.out.println("Consumer:" +monitor.pop().id); } } } class Monitor { Goods[] buffer = new Goods[10 ]; int gCount = 0 ; public synchronized void push (Goods goods) { while (gCount == buffer.length){ try { this .wait(); } catch (InterruptedException e) { e.printStackTrace(); } this .notifyAll(); } buffer[gCount++] = goods; this .notifyAll(); } public synchronized Goods pop () { while (gCount == 0 ){ try { this .wait(); } catch (InterruptedException e) { e.printStackTrace(); } this .notifyAll(); } Goods goods = buffer[--gCount]; this .notifyAll(); return goods; } }
信号灯法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class Cooperation2 { public static void main (String[] args) { TV tv = new TV(); new Actor(tv).start(); new Fans(tv).start(); } } class Actor extends Thread { TV tv; public Actor (TV tv) { this .tv = tv; } @Override public void run () { for (int i=0 ; i<10 ; i++){ if (i%2 == 0 ){ this .tv.play("節目 " + i); }else { this .tv.play("廣告 " + i); } } } } class Fans extends Thread { TV tv; public Fans (TV tv) { this .tv = tv; } @Override public void run () { for (int i=0 ; i<10 ; 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(); } } this .voice = voice; System.out.println("表演 " +voice +" ing" ); this .notifyAll(); this .flag = !flag; } public synchronized void watch () { if (flag){ try { this .wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("觀看 " + voice +" ing" ); this .notifyAll(); this .flag = !flag; } }
线程池&callable接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import java.io.IOException;import java.util.concurrent.Callable;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;public class TestCallable implements Callable <Boolean > { private String url; private String name; public TestCallable (String url,String name) { this .url = url; this .name = name; } @Override public Boolean call () throws IOException { WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url,name); System.out.println("下载的文件名为:" +name); return true ; } public static void main (String[] args) throws Exception { TestCallable t1 = new TestCallable("https://www.baidu.com/img/dong_f6764cd1911fae7d460b25e31c7e342c.gif" ,"百度劳动节1.gif" ); TestCallable t2 = new TestCallable("http://a4.att.hudong.com/72/82/19300000009075130804824786610.jpg" ,"恐龙2.jpg" ); TestCallable t3 = new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1588579963142&di=69d697e367f8b3bc144a759a51377c5a&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20171110%2Fbd4b16a2eaa74eb683704ff4f85dc813.jpeg" ,"一人之下3.jpg" ); ExecutorService ser = Executors.newFixedThreadPool(3 ); Future<Boolean> r1 = ser.submit(t1); Future<Boolean> r2 = ser.submit(t2); Future<Boolean> r3 = ser.submit(t3); boolean rs1 = r1.get(); boolean rs2 = r2.get(); boolean rs3 = r3.get(); System.out.println(rs1); System.out.println(rs2); System.out.println(rs3); ser.shutdown(); } }
Runnable接口使用execute
而不是submit