目录
01,八锁问题
- 对象锁(
synchronized method{}
)
在普通方法上加synchronized关键字,是对象锁,锁的是当前方法的调用者,只要不是同一个实例,就不需要抢锁,例如:一个类的两个不同的实例,是不需要抢锁的。
- 类锁(
static sychronized method{}
)
在静态方法上加synchronized关键字,是类锁,锁的是类,只要是同一个类的实例都需要抢锁
-
注意:对象锁和类锁不是同一个锁(一定记住这句话)
-
8锁问题:关于锁的8个问题,只要理解上面的话,这些问题就迎刃而解了。
问题一
- 示例程序
public class Test01 {
public static void main(String[] args) {
Phone phone =new Phone();
new Thread(()->{
phone.sendMsg();
},"thread_A").start();
//使用java.util.concurrent.TimeUtil来进行睡眠
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"Thread_B").start();
}
}
class Phone{
public synchronized void sendMsg(){
System.out.println("sendMsg.....");
}
public synchronized void call(){
System.out.println("call....");
}
}
- 示例分析:
synchronized 锁只有一个锁,线程A先拿到锁,所以先执行线程A中的打印操作,在执行线程B
输出结果为:
sendMsg.....
call....
问题二
- 示例程序
public class Test01 {
public static void main(String[] args) {
Phone phone =new Phone();
new Thread(()->{
phone.sendMsg();
},"thread_A").start();
//使用java.util.concurrent.TimeUtil来进行睡眠
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"Thread_B").start();
}
}
class Phone{
public synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg.....");
}
public synchronized void call(){
System.out.println("call....");
}
}
- 示例分析
因为有锁,如果不设置线程等待【wait】,使用synchronized修饰的方法会【抱着锁睡觉】
输出结果为:
sendMsg.....
call....
问题三
- 示例程序
public class Test02 {
public static void main(String[] args) {
Phone2 phone =new Phone2();
new Thread(()->{
phone.sendMsg();
},"thread_A").start();
//使用java.util.concurrent.TimeUtil来进行睡眠
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"Thread_B").start();
new Thread(()->{
phone.hello();
},"Thread_C").start();
}
}
class Phone2{
public synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg.....");
}
public synchronized void call(){
System.out.println("call....");
}
//没有锁,不受锁的影响
public void hello(){
System.out.println("hello....");
}
}
- 示例分析
synchronized锁,同被synchronized关键字修饰的方法才会抢这个锁
由于hello没有被synchronized关键字修饰,所以不用抢这个锁
虽然Thread_A先拿到了锁,但是它睡眠了,所以CPU会调度执行下一个线程,又因为Thread_B
需要等Thread_A释放锁,所以执行结果为:
hello....
sendMsg.....
call....
问题四:
- 示例程序
public class Test02 {
public static void main(String[] args) {
Phone2 phone =new Phone2();//对象一
Phone2 phone2=new Phone2();//对象二
new Thread(()->{
phone.sendMsg();
},"thread_A").start();
//使用java.util.concurrent.TimeUtil来进行睡眠
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"Thread_B").start();
}
}
class Phone2{
//因为有锁,如果不设置行线程等待,使用synchronized修饰的方法会【抱着锁睡觉】
public synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg.....");
}
public synchronized void call(){
System.out.println("call....");
}
}
- 示例分析
synchronized 锁修饰普通方法,锁的是【方法的调用者(phone),也就是对象】,由于有两个调用者,
所以有两个锁,不用抢
虽然先执行了Thread_A线程,但是它睡眠了,所以CPU会调度执行下一个线程(线程中的方法带锁,但是不是同一把锁)
输出结果:
call....
sendMsg.....
问题五
- 示例程序
public class Test03 {
public static void main(String[] args) {
Phone3 phone =new Phone3();
new Thread(()->{
phone.sendMsg();
},"thread_A").start();
//使用java.util.concurrent.TimeUtil来进行睡眠
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"Thread_B").start();
}
}
class Phone3{
//静态方法:类加载就有了,不属于任何对象,属于类的实例共有此时锁的是Class,也就是类锁
public static synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg.....");
}
public static synchronized void call(){
System.out.println("call....");
}
}
- 示例分析
sychronized修饰静态方法,锁的是类【Class】,所有该类的实例,还需要抢锁
输出结果:
sendMsg.....
call....
问题六
- 示例程序
public class Test03 {
public static void main(String[] args) {
Phone3 phone =new Phone3();
Phone3 phone2=new Phone3();
new Thread(()->{
phone.sendMsg();
},"thread_A").start();
//使用java.util.concurrent.TimeUtil来进行睡眠
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"Thread_C").start();
}
}
class Phone3{
public static synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg.....");
}
public static synchronized void call(){
System.out.println("call....");
}
}
- 示例分析
sychronized修饰静态方法,锁的是类【Class】,所有该类的实例,还需要抢锁
由于锁的是【Class】,类锁,就是只要是同一个类的对象,就算两个对象也需要抢锁
输出结果:
sendMsg.....
call....
问题七
- 示例程序
public class Test04 {
public static void main(String[] args) {
Phone4 phone =new Phone4();
Phone4 phone2=new Phone4();
new Thread(()->{
phone.sendMsg();
},"thread_A").start();
//使用java.util.concurrent.TimeUtil来进行睡眠
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"Thread_B").start();
}
}
class Phone4{
public static synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg.....");
}
public synchronized void call(){
System.out.println("call....");
}
}
- 示例分析
被synchronized关键字修饰的静态方法的锁的是类【Class】
被synchronized关键字修饰的普通方法的锁的是对象【调用者】
不用抢锁,虽然先执行Thread_A,但是它睡眠了,所以CPU会调度其他线程执行(由于不是同一把锁)
输出结果:
call....
sendMsg.....
问题八(注意和问题七的对比)
- 示例程序
public class Test04 {
public static void main(String[] args) {
Phone4 phone =new Phone4();
new Thread(()->{
phone.sendMsg();
},"thread_A").start();
//使用java.util.concurrent.TimeUtil来进行睡眠
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"Thread_B").start();
}
}
class Phone4{
public static synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg.....");
}
public synchronized void call(){
System.out.println("call....");
}
}
- 示例分析
虽然是同一个对象,但是一个拿的是对象锁,一个是类锁,是两把锁,不用抢锁
运行结果:
call....
sendMsg.....
02,集合类的安全问题
线程安全的List:java.util.concurrent.CopyOnWriteArrayList
List:
- 单个线程十分安全
- 多线程不安全
- 并发下会报错,
java.util.ConcurrentModificationException
:并发修改异常
单线程下的List
public class ListTest {
public static void main(String[] args) {
//单个线程十分安全,但是并发下安全吗?
List<String> lists= Arrays.asList("aismall","is","very","beautiful");
lists.forEach(System.out::println);
}
}
多下程下的List
public class ListTest {
public static void main(String[] args) {
//并发下会报错:并发下ArrayList不安全
List<String> list=new ArrayList<>();
for (int i=0;i<20;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
如何解决多线程下的List的不安全问题
解决方案1:使用vector(默认安全)
List<String> list=new Vector();
这个方法过于老旧
解决方法2:使用Collections中的静态方法,synchronizedList
List<String> list=Collections.synchronizedList(new ArrayList<>());
解决方法3:使用JUC包下的copyOnWrite(写入时复制)
List<String> list=new CopyOnWriteArrayList<>();
问题1:CopyOnWrite比Vector优越在哪里?
Vector的add方法添加了synchronized锁,效率比较慢
CopyOnWrite里面使用的是lock锁,效率快一点
具体的可以查看vector.add和CopyOnWriteArrayList的源码
public class ListTest {
public static void main(String[] args) {
List<String> list=Collections.synchronizedList(new ArrayList<>());
for(int i=0;i<10;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
List<String> list2=new CopyOnWriteArrayList<>();
for(int i=0;i<10;i++){
new Thread(()->{
list2.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
线程安全的map:java.util.concurrent.ConcurrentHashMap
map:
- 单个线程十分安全
- 多线程不安全
- 并发下会报错,
java.util.ConcurrentModificationException
:并发修改异常
多下程下的map
public class MapTest {
public static void main(String[] args) {
//默认构造函数等价于什么? new HashMap(16,0.75);
//HashMap默认容量为16,超过12个就会进行扩容
Map<String,String> map=new HashMap<>();
for(int i=0;i<20;i++){
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
如何解决多线程下的map的不安全问题
解决办法:使用java.util.concurrent.ConcurrentHashMap
具体可查看ConcurrentHashMap源码
public class MapTest {
public static void main(String[] args) {
//map 是这样用的吗? 不是,工作中不用HashMap
Map<String,String> map=new ConcurrentHashMap<>();
for(int i=0;i<20;i++){
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
线程安全的Set:java.util.concurrent.CopyOnWriteArraySet
Set:
- 单个线程十分安全
- 多线程不安全
- 并发下会报错,
java.util.ConcurrentModificationException
:并发修改异常
多下程下的Set
public class SetTest {
public static void main(String[] args) {
//单个线程十分安全,但是并发下安全吗?
//注意:Set和List同属于java.util.Collection接口
//并发下会报错:并发下Set不安全
Set<String> set=new HashSet<>();
for(int i=0;i<20;i++){
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
如何解决多线程下的set的不安全问题
解决方案1:使用vector(默认安全)
List<String> list=new Vector();
这个方法过于老旧
解决方法2:使用Collections中的静态方法,synchronizedList
Set<String> set= Collections.synchronizedSet(new HashSet<>());
解决方法3:使用JUC包下的copyOnWrite(写入时复制)
List<String> list=new CopyOnWriteArraySet<>();
问题1:CopyOnWrite比Vector优越在哪里?
Vector的add方法添加了synchronized锁,效率比较慢
CopyOnWrite里面使用的是lock锁,效率快一点
public class SetTest {
public static void main(String[] args) {
Set<String> set= Collections.synchronizedSet(new HashSet<>());
for(int i=0;i<20;i++){
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
Set<String> set2=new CopyOnWriteArraySet<>();
for(int i=0;i<20;i++){
new Thread(()->{
set2.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
03,java.util.concurrent.Callable接口
- 在前面我们已经把
java.util.concurrent.locks
包下的三个接口讲完了,现在我们开始陆续介绍java.util.concurrent
包下的一些东西 - 我们先介绍一下
java.util.concurrent
包下的一个Callable
接口 - 可以理解为
多线程创建的第三种方式
Callable接口在:java.util.concurrent包下
功能:类似于Runnable接口,可以创建一个可以被其他线程执行的实例
差别:Callable接口
- 可以有返回值
- 可以抛出异常
- 方法不同,一个是run(),一个是call()
传统方式创建线程
传统方式:
- 1.编写一个类继承Runnab接口
- 2.重写Runnable接口中的run()方法,
- 3.创建一个线程类的实现:MyThread mythread=new MyThread();
- 4.把线程的实现类丢入到新建的线程类中:new Thread(mythread).start();
缺点:没有返回值注意:【通过查Thread类发现,此类的构造方法中只能接收Runnable的实现】
public class CallableTest01 {
public static void main(String[] args) {
MyThread myThread=new MyThread();
new Thread(myThread,"Thread_A").start();
new Thread(myThread,"Thread_B").start();
}
}
class MyThread implements Runnable{
@Override
public void run(){
System.out.println("传统方式创建线程.....");
}
}
使用Callable接口创建线程
注意:通过查Thread类发现,此类的构造方法中只能接收Runnable的实现类
所以要想使用Callable创建线程,必须要和Thread或者Runnable接口扯上关系
经过查看JDK文档发现:
Runnable有个实现类FutureTask,在java.util.concurrent包下面
FutureTask有个构造可以接收Callable接口的实现类
于是乎我们可以这样
1.创建一个类实现Callable接口(通过泛型指定返值的类型)
2.把实现类的实例放到FutureTask的构造中,FutureTask是Runnable接口的实现类
3.把FutureTask的实例放到Thread中用来创建线程
Callable可以有返回值,返回值在FutureTask里面,使用get方法获取返回值
public class CallableTest01 {
public static void main(String[] args) {
MyThread2 myThread2=new MyThread2();
FutureTask futureTask=new FutureTask(myThread2);
new Thread(futureTask,"Thread_A").start();
new Thread(new FutureTask<String>(new MyThread2()),"Thread_B").start();
try {
String returnValue=(String) futureTask.get();//此方法会产生阻塞,一般往后放
System.out.println(returnValue);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//注意:泛型的参数等于方法的返回值
class MyThread2 implements Callable<String>{
@Override
public String call() throws InterruptedException{
System.out.println("Callable.call.....");
return "aismall";
}
}