一Lock锁
JDK5以后的针对线程的锁定操作和释放操作
Lock lock = new ReentrantLock(); 获得对象
lock.lock(); 在操作数据的代码前加锁
lock.unlock(); 在操作数据的代码后释放锁
二 死锁问题
1 描述
多线程环境中,出现了锁的嵌套,可能会发生死锁。
2 代码体现
public void run(){
if(flag){
synchronized(T02MyLock.objA){
System.out.println("if objA");
synchronized(T02MyLock.objB){
System.out.println("if objB");
}
}
}else{
synchronized(T02MyLock.objB){
System.out.println("else objB");
synchronized(T02MyLock.objA){
System.out.println("else objA");
}
}
}
}
此时,如果两个线程分别走 true 和 false 两个分支,则有可能发生死锁
三 线程间通信问题(生产者和消费者多线程问题)
代码1:
public class Student{
public String name;
public int age;
}
public class GetThread implements Runnable{
private Student s;
public GetThread(s){
this.s = s;
}
public void run(){
System.out.println(s.name + " " + s.age);
}
}
public class SetThread implements Runnable{
private Student s;
public SetThread(s){
this.s = s;
}
public void run(){
s.name = "";
s.age = 27;
}
}
public class StudentDemo{
public static void main(String[] args) {
Student s = new Student();
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
t1.start();
t2.start();
}
}
资源类: Student
设置数据类:SetThread(生产者)
获取数据类:GetThread(消费者)
测试类:StudentDemo
具体描述:多线程环境下,生产和消费同时执行。处理相同的资源。在处理相同资源时需要同步,即线程间通信。
1)出现的问题1:
线程安全问题。
解决方案:可以加锁。
2)完善思路:
生产、消费是个不断的过程,所以可以在run函数里面,加上while()循环。(当然本例的实际意义不明显)
可以区分每次具体生产的不一样,所以可以在set函数里,(通过新增一个变量),来控制每次生产的不同种类的东西(对应的student,就是每次设置不同的名字)
3)出现的问题2:
可能会连续生产却没有消费,或者对同一个商品进行多次消费。(体现在本例中就是,同一个名字被不停的读出,而来不及设置另一个名字)
解决方案:等待唤醒机制。
四 等待唤醒机制(代码及理解)
代码2:
(是对代码1的改进。其中测试类没有变,所以没有贴出代码)
public class SetThread implements Runnable{
private Student s;
private int x = 0;
public SetThread(s){
this.s = s;
}
public void run(){
while(true){
if( x % 2 ){
s.set("林青霞", 27);
}else{
s.set("刘意", 30);
}
x++;
}
}
}
public class GetThread implements Runnable{
private Student s;
public GetThread(s){
this.s = s;
}
public void run(){
while(true){
s.get();
}
}
}
public class Student{
private String name;
private int age;
private boolean flag;
public synchronized void set(String name, int age){
if(flag){
try{
this.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
this.name = name;
this.age = age;
flag = true;
this.notify();
}
public synchronized void get(){
if(!flag){
try{
this.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println(name + " " + age);
flag = false;
this.notify();
}
}
wait()和notify(),为了确保set()和get()可以依次执行
1)这些方法必须要标示所属的锁
当A锁上的线程wait后,只能由A锁的notify唤醒
2)这三个方法都定义在Object中
由于锁可以是任意对象,那么能被任意对象调用的方法也应定义在Object中。
wait()和sleep()的区别
wait:可以指定时间也可以不指定时间。不指定时间时,只能由对应的notify和notifyAll唤醒。
sleep:必须指定时间,记满时间后,自动从冻结状态转成运行状态。(临时阻塞状态)
注:线程的状态转换图中,从运行 -> 就绪,是因为被别的夺取了执行权
五 其他
1 线程组
ThreadGroup:可以通过Thread的构造函数明确新线程对象所属的线程组。
(默认在main线程组中。)
线程组的好处,可以对多个同组线程,进行统一的操作。默认都属于main线程组。
2 线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
// 这里传一个Runnable()的子类对象
pool.submit(my1);
pool.submit(my2);
pool.shutdown();
第一行的声明语句处,可以指定大小。
3 线程的匿名内部类
public static void main(String[] args) {
// 继承Thread类来实现多线程
new Thread(){
public void run(){
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}.start();
// 实现Runnable接口来实现多线程
new Thread(new Runnable(){
public void run(){
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}){}.start();
// 更有难度的
new Thread(new Runnable(){
public void run(){
for (int x = 0; x < 10; x++) {
System.out.println("hello" + ":" + x);
}
}
}){
public void run(){
for (int x = 0; x < 10; x++) {
System.out.println("world" + ":" + x);
}
}
}.start();
}
需要注意的是,第三种方式,最终是输出 world 的,而非 hello
六 单例设计模式
// 单例模式:保证类在内存中只有一个对象。
1 饿汉式
public class Student {
private Student(){}
private static Student s = new Student();
public static Student getStudent(){
return s;
}
}
一开始就给成员变量赋值(或者说类一加载就创建对象)
2 懒汉式
public class Teacher {
private Teacher(){}
private static Teacher t;
public synchronized static Teacher getTeacher(){
if( t == null ){
t = new Teacher();
}
return t;
}
}
用的时候,才去创建对象
存在的问题:
多线程环境中,共享数据,多条操作语句,所以会有线程安全问题
所以在上例中,加了synchronized