目录
线程和进程
进程:
- 一个进程往往可以包含多个线程,至少包含一个!
- Java默认有几个线程? 2 个 :mian、GC。
线程:开了一个进程 Typora,写字,自动保存(线程负责的)
Java无法直接开线程,是调用native本地方法,底层的C++,JAVA无法直接操作硬件
并发、并行
并发编程:并发,并行
并发:多个线程操作同一个资源
- 并发:cpu只有一个核:多线程操作同一个资源(cpu通过线程间的快速交替,模拟出来多条线程,看似并行,实际串行)
- 并行:cpu有多个核,多个线程可以同时执行,可以通过线程池完成
并发编程的本质:充分利用CPU的资源
public class test01 {
public static void main(String[] args) {
//获取CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
线程状态
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待(死死的等)
WAITING,
//超时等待(到期就不等了)
TIMED_WAITING,
//终止
TERMINATED;
}
wait/sleep区别
来自不同的类
wait()来自Object类,sleep()来自Thread类
关于锁的释放
wait()会释放锁,sleep()不会释放锁,可以理解为抱着锁睡觉
使用范围不同
wait()只能在同步代码块中使用,sleep()可以在任何地方使用
是否需要捕获异常
wait()不需要捕获异常,sleep()必须要捕获异常
LOCL锁(重点)
公平锁:十分公平,先来后到,排队
非公平锁:不公平,可以插队
(默认是非公平锁,是为了公平,比如一个线程要3s,另一个线程要3h,难道一定要让3h的锁先来就先执行吗)
1 传统的synchronized锁
- 在公司真正的多线程开发中,线程就是一个单独的资源类,没有任何附属的操作(类中只有属性和方法),为了降低耦合性,不会用类去实现接口,因为实现类接口就不是OOP编程了,而且实现了接口的话耦合性变高,如果让类实现了Runnable,这个类就只是一个线程类了
//lock三部曲
// 1. new ReentrantLock()
// 2. lock.lock(); //加锁
// 3. finally => lock.unlock(); //解锁
class Ticket2 {
//属性,方法
private int num = 30;
Lock lock = new ReentrantLock();
public void sale() {
lock.lock(); //加锁
try {
// 业务代码
if (num > 0) {
System.out.println(Thread.currentThread().getName()+"卖出了第"+(num--)+"张票,剩余:"+num);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //解锁
}
}
}
2 Synchronized和Lock锁的区别
- Synchronized:内置的Java关键字,Lock:是一个java类
- Synchronized:无法判断获取锁的状态,Lock:可以判断是否获取了锁
- Synchronized:会自动释放锁,Lock:必须要手动释放锁,如果不释放锁,会死锁
- Synchronized:线程1(获得锁,阻塞),线程2(等待,傻傻的等),Lock:Lock锁不一定会等待,Lock有一个方法Lock.tryLock();会去尝试获取锁
- Synchronized:可重入锁,不可以在中断,非公平, Lock:可重入,可以判断锁,可以设置公平或者非公平
- Synchronized:适合锁少量的代码同步问题,Lock:适合锁大量的同步代码
synchronized写法
public synchronized void increment() throws InterruptedException {
while (num != 0) {
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程,我+1完了
this.notifyAll();
}
lock Condition写法
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void increment() throws InterruptedException {
lock.lock();
try {
while (num != 0) {
//等待
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程,我+1完了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
锁是什么,如何判断锁的是谁
问题1:标准情况下,先打印发短信,还是先打印打电话?结果是:先打印发短信,后打印打电话
原因:不能回答先调用A线程,这是错误的,不是先调用先执行,这是锁的问题,因为被Synchronized修饰的 方法,锁的对象是方法的调用者,所以调用两个方法的对象都是phone,但是现在phone只有一个,也就是说着两个方 法现在用的是同一把锁,谁先拿到,谁就先执行
public class Test1 {
public static void main(String[] args) {
phone phone = new phone();
new Thread(()->{ phone.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone.call(); },"B").start();
}
}
class phone{
//synchronized 锁的对象是方法的调用者
public synchronized void sendSms() {
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
问题2:给发短信方法加延时4s,程序的执行情况
结果:经过4s之后打印发短信,然后立马打印打电话。
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
问题3:当调用普通方法,而不是synchronized方法时,程序的执行情况
结果:1秒先打印Hello,4秒后打印发短信
先执行普通方法,因为普通方法没有锁,不受锁的影响
public class Test1 {
public static void main(String[] args) {
phone phone = new phone();
new Thread(()->{ phone.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone.sayHello(); },"B").start();
}
}
class phone{
//synchronized 锁的对象是方法的调用者
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
public void sayHello()
{
System.out.println("hello");
}
}
问题4:两个对象分别调用synchronized方法时,程序的执行情况
结果:先打电话,后发短信。
两个对象,两个调用者,两把锁,因为锁不一样,所以耗时短的先输出。
public static void main(String[] args) {
//两个对象,两个调用者,两把锁
phone phone1 = new phone();
phone phone2 = new phone();
new Thread(()->{ phone1.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone2.call(); },"B").start();
}
问题5:增加两个静态的同步方法,只有一个对象,程序的执行情况
结果:先发短信,后打电话。
public class Test1 {
public static void main(String[] args) {
phone phone = new phone();
new Thread(()->{ phone.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone.call(); },"B").start();
}
}
class phone{
//static 静态方法,类一加载就有了,锁的是Class
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
问题6:两个静态的同步方法,两个对象,程序的执行情况
结果:先发短信,后打电话。
两个对象的Class模板只有一个,static锁的是Class
public class Test1 {
public static void main(String[] args) {
phone phone1 = new phone();
phone phone2 = new phone();
new Thread(()->{ phone1.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone2.call(); },"B").start();
}
}
问题7:一个静态同步方法,一个普通同步方法,只有一个对象,程序的执行情况
结果:先打电话,后发短信。
不是同一个锁,锁调用者的不需要等待,锁Class的要等待
public class Test1 {
public static void main(String[] args) {
phone phone = new phone();
new Thread(()->{ phone.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone.call(); },"B").start();
}
}
class phone{
//静态同步方法,锁的是Class模板
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通同步方法,锁的是调用者,不需要去等待锁
public synchronized void call() {
System.out.println("打电话");
}
}
问题8:一个静态同步方法,一个普通同步方法,两个对象,程序的执行情况
结果:先打电话,后发短信。
两个锁,还是锁的对象不一样
public static void main(String[] args) {
phone phone1 = new phone();
phone phone2 = new phone();
new Thread(()->{ phone1.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone2.call(); },"B").start();
}
小结
普通同步方法:(new this) 锁的是调用者,是一个具体的对象
静态同步方法:(static Class) 锁的是Class模板,是唯一的