学习一时爽,一直学习一直爽
Hello,大家好,我是 もうり,一个从无到有的技术+语言小白。
https://blog.csdn.net/weixin_44510615/article/details/102617286
Java多线程往往决定Java水平
在 Java 中实现多线程有两种手段,一种是继承 Thread 类,另一种就是实现 Runnable 接口。
Java 提供了三种创建线程的方法:
RunnabeDemo.java
/** * @author: 毛利 */class RunnableDemo implements Runnable { private Thread t; private String threadName; RunnableDemo( String name) { threadName = name; System.out.println("Creating " + threadName ); } public void run() { System.out.println("Running " + threadName ); try { for(int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // 让线程睡眠一会0.05 Thread.sleep(50); } }catch (InterruptedException e) { System.out.println("Thread " + threadName + " interrupted."); } System.out.println("Thread " + threadName + " exiting."); } public void start () { System.out.println("Starting " + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } }}
TestThread.java
/** * @author: 毛利 */public class TestThread { public static void main(String args[]) { RunnableDemo R1 = new RunnableDemo( "Thread-1"); R1.start(); RunnableDemo R2 = new RunnableDemo( "Thread-2"); R2.start(); }}
单线程
/** * @author: 毛利 */public class demo1 extends Thread { // 覆盖 run 方法 @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(this.getName() + ",i=" + i); } } public static void main(String[] args) { // 程序开始,执行的线程名字叫做main System.out.println("程序开始,执行的线程名字叫做" + Thread.currentThread().getName()); // 创建demo1对象 demo1 demo1 = new demo1(); // 执行run方法 demo1.start(); /* Thread-0,i=0 Thread-0,i=1 Thread-0,i=2 Thread-0,i=3 Thread-0,i=4 */ }}
如果我再添加一共 demo2 线程
demo1 demo2 = new demo1();demo2.start();
发现了
竟然是同步运行的
是时候吹吹 synchronized
修饰符,难度使用 synchronized
修饰符就一定同步了?
现在使用继承 Runnable 方法来看看毛利是不是穷人
/** * @author: 毛利 */public class demo2 implements Runnable{ /* 创建生产者和消费者的概念 */ // 一开始毛利没有钱的 private int count = 0; // 毛利我挣钱 public synchronized void addMoney(){ double money = Math.floor(Math.random()*100); count += money; System.out.println( "毛利我挣了"+ money + "元"); } // 毛利我花钱 public synchronized void subMoney(){ double money = Math.floor(Math.random()*100); if (count - money < 0 ){ count -= money; System.out.println("毛利你又花了"+ money + "你现在负债啊啊,准备喝西北风了"); } else { count -= money; System.out.println("毛利又花了" + money + ",准备吃土了"); } } // 看看毛利口袋 public void lookMoney(){ System.out.println("毛利现在口袋只有"+count); } public static void main(String[] args) { demo2 maoli = new demo2(); maoli.run(); } @Override public void run() { for (int j =0 ; j<10;j++){ addMoney(); subMoney(); lookMoney(); } }}
运行结果如下,果然我还是一个穷人,天天喝西北风
毛利我挣了79.0元毛利你又花了95.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-16毛利我挣了2.0元毛利你又花了35.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-49毛利我挣了2.0元毛利你又花了14.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-61毛利我挣了34.0元毛利你又花了26.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-53毛利我挣了36.0元毛利你又花了63.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-80毛利我挣了57.0元毛利你又花了7.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-30毛利我挣了67.0元毛利你又花了66.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-29毛利我挣了68.0元毛利你又花了63.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-24毛利我挣了76.0元毛利你又花了96.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-44毛利我挣了38.0元毛利你又花了80.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-86
这次程序是一个一个运行的。不是一段一段的
这次毛利创建两个线程看看毛利是否吃土
public static void main(String[] args) throws InterruptedException { demo2 maoli = new demo2(); // maoli.run(); Thread thread1 = new Thread(maoli); Thread thread2 = new Thread(maoli); //start() 本身就是执行run thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("终于知道毛利是一个穷光蛋了"); }
两个线程同步运行
thread1.start();thread2.start(); 意思是开启了 thread1 和 thread2,也就是两个子线程都开始执行 run 方法。
最后毛利果然还是喝西北风
thread1.join();thread2.join(); 意思是 thread1 子线程执行完再执行主线程,thread2 子线程执行完再执行主线程. 最后输出毛利是穷光蛋
毛利我要专业点说说 synchronized
如果一个对象对多个线程可见,则对该对象变量的所有读取和写入都是通过同步方法完成的。
通俗:能够保证你在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。
synchronized 是 Java 的关键字,是最基本的互斥同步手段,是并发编程必学内容。
Java 并发一道好题
/** * @author: 毛利 */public class demo3 implements Runnable { // main 方法静态,所以num必须是加static static int num = 0; @Override public void run() { for (int i = 0 ;i<10000;i++){ num++; } } public static void main(String[] args) throws InterruptedException { demo3 main = new demo3(); Thread thread1 = new Thread(main); Thread thread2 = new Thread(main); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(num); }}
代码如下创建两个线程跑 run 方法,最后我想知道这个 num 到底是多少?
2 个线程,10000*2=20000
NO? 纳里?
如果两个线程存在一种现象不是同步所有读取和写入,那么就不可能是 20000
答案是 0-20000 之间的任何一个数
加上 synchronized 问题就解决了
@Overridepublic synchronized void run() { for (int i = 0 ;i<10000;i++){ num++; }}
当然还可以不用在函数入口加, 添加 synchronized(this)
一样的
@Overridepublic void run() { synchronized(this) { for (int i = 0; i < 10000; i++) { num++; } }}
当两个并发线程访问同一个对象 object 中的这个 synchronized(this) 同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
上面的代码修饰的 synchronized 是非静态方法,如果修饰的是静态方法(static)含义是完全不一样的。
但是如果我 new 两个对象,这样就又 2 个 object 的对象锁,答案又变了
/** * @author: 毛利 */public class demo3 implements Runnable { // main 方法静态,所以num必须是加static static int num = 0; @Override public void run() { synchronized(this) { for (int i = 0; i < 10000; i++) { num++; } } } public static void main(String[] args) throws InterruptedException { // new两个对象 Thread thread1 = new Thread(new demo3()); Thread thread2 = new Thread(new demo3()); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(num); // 0-20000 }}
是时候抛出对象锁,不过先了解下类锁
类锁对应的关键字是 static sychronized,是一个全局锁,无论多少个对象否共享同一个锁(也可以锁定在该类的 class 上或者是 classloader 对象上),同样是保障同一个时刻多个线程同时访问同一个 synchronized 块,当一个线程在访问时,其他的线程等待。
对象锁对应 synchronized 关键字,当多个线程访问多个实例时,它们互不干扰,每个对象都拥有自己的锁,如果是单例模式下,那么就是变成和类锁一样的功能。
对象锁防止在同一个时刻多个线程访问同一个对象的 synchronized 块。如果不是同一个对象就没有这样子的限制。
Synchronized(对象锁)和 Static Synchronized(类锁)的区别
/** * @author: 毛利 */public class demo4 { // Synchronized(对象锁) public synchronized void test1() { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException e) { } } } // Static Synchronized(类锁) public static synchronized void test2() { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException e) { } } } public static void main(String[] args) { demo4 demo4 = new demo4(); // 采用Runable 复写 run Thread test1 = new Thread(new Runnable() { public void run() { demo4.test1(); } }, "test1"); Thread test2 = new Thread(new Runnable() { public void run() { demo4.test2(); } }, "test2"); test1.start(); test2.start(); } /* test2 : 4 test1 : 4 test2 : 3 test1 : 3 test2 : 2 test1 : 2 test1 : 1 test2 : 1 test2 : 0 test1 : 0 */}
下面使用同一个对象的多个实例
public static void main(String[] args) { demo4 demo4 = new demo4(); // 采用Runable 复写 run Thread test1 = new Thread(new Runnable() { public void run() { demo4.test1(); } }, "test1"); Thread test2 = new Thread(new Runnable() { public void run() { demo4.test1(); } }, "test2"); Thread test3 = new Thread(new Runnable() { public void run() { demo4.test2(); } }, "test3"); Thread test4 = new Thread(new Runnable() { public void run() { demo4.test2(); } }, "test4"); test1.start(); test2.start(); test3.start(); test4.start(); }
果然 test2 和 test4 不会马上调用
test1 : 4test3 : 4test3 : 3test1 : 3test3 : 2test1 : 2test3 : 1test1 : 1test1 : 0test3 : 0test2 : 4test4 : 4test4 : 3test2 : 3test2 : 2test4 : 2test2 : 1test4 : 1test4 : 0test2 : 0
现在使用多个对象来调用
public static void main(String[] args) { // 采用Runable 复写 run Thread test1 = new Thread(new Runnable() { public void run() { new demo4().test1(); } }, "test1"); Thread test2 = new Thread(new Runnable() { public void run() { new demo4().test1(); } }, "test2"); Thread test3 = new Thread(new Runnable() { public void run() { new demo4().test2(); } }, "test3"); Thread test4 = new Thread(new Runnable() { public void run() { new demo4().test2(); } }, "test4"); test1.start(); test2.start(); test3.start(); test4.start(); }
test4 是不会马上调用的,因为是类锁,不是对象锁
test1 : 4test3 : 4test2 : 4test3 : 3test1 : 3test2 : 3test2 : 2test3 : 2test1 : 2test1 : 1test2 : 1test3 : 1test2 : 0test1 : 0test3 : 0test4 : 4test4 : 3test4 : 2test4 : 1test4 : 0
对象锁防止在同一个时刻多个线程访问同一个对象的 synchronized 块。
如果不是同一个对象就没有这样子的限制。但是类锁就不同了。
synchronized 是对类的当前实例进行加锁,防止其他线程同时访问该类的该实例的所有 synchronized 块,注意这里是 “类的当前实例”, 类的两个不同实例就没有这种约束了。