1 线程
线程是一个进程里不同的执行路径。
进程是一个静态的概念,进程开始执行其实是他的主线开始执行了(主线程:main方法)
在同一个时间点上,CPU只有一个线程在执行,真正的多线程:多核
Java的线程是通过Java.lang.Thread类类实现的,可以通过创建Thread的实例来创建新的线程,Thread类里的run方法:你在run里写哪些东西,这个线程就执行哪些东西。start()方法启动一个新的线程。
创建线程的方法:
(1)实现Runnable接口,编译器就知道这是一个线程类。(Thread类本身就实现了Runnable接口)
Thread(Runnable target)
有多态存在,Runnable target是父类的引用,当我们传的时候传的是子类对象,即父类引用指向子类对象。这又是重写,调的是自己的run方法。
(2)继承Thread类
能使用接口就使用接口,因为继承不能多继承。
线程状态转换:
创建-start()(start只是就绪状态,并不是就开始运行了)
2 线程同步
Java引入了对象互斥锁,保证共享数据操作的完整性,每个对象都对应于一个可称为“互斥锁”的标记,标记保证在任一时刻,只能由一个线程访问该对象。
//线程同步
public class TestThread4 implements Runnable {
Timer timer=new Timer();
public static void main(String[] args) {
TestThread4 test=new TestThread4();
Thread t1=new Thread(test);
Thread t2=new Thread(test);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
public void run() {
timer.add(Thread.currentThread().getName());
}
}
class Timer {
private static int num=0;
//
public void add(String name) {
synchronized(this) {//锁定某一个东西 锁定synchronized大括号里的东西 ----互斥锁
num++;
try {Thread.sleep(1);}
catch(InterruptedException e) {}
System.out.println(name+",你是第"+num+"个使用timer的线程");
}
}
}
//另一种写法
/*class Timer {
private static int num=0;
public synchronized void add(String name) {//在执行这个方法时候,锁定当前对象(即锁定this)
num++;
try {Thread.sleep(1);}
catch(InterruptedException e) {}
System.out.println(name+",你是第"+num+"个使用timer的线程");
}
}*/
分析:t1和t2都是传了test这个对象产生的,它们执行的是同一个test对象的run方法,而且run里面是同一个timer对象,也就是说他们要访问同一个timer对象,执行同一个timer的add方法,如果不加锁,输出结果会是:
t1,你是第2个使用timer的线程
t2,你是第2个使用timer的线程
因为在t1的执行过程种,在调用add方法,在num++变为1后,t1休眠了,这时候t2也调用了add方法,num++,因为他们俩是同一个test,同一个timer,同一个num,所以当t1醒来过后,num已经是2了。
这里为了看的更清楚,写了个sleep()方法,实际如果不写,实际情况中也有很多这种访问同一个资源导致不一致的情况。
加了锁过后,输出正确:
t1,你是第1个使用timer的线程
t2,你是第2个使用timer的线程
synchronized是关键字,来与对象的互斥锁联系,当某个对象synchronized修饰时,表面该对象在任一时刻只能由一个线程访问。使用方法:
(1) synchronized 方法
通过在方法声明中加入 synchronized关键字来声明,语法如下:
|
synchronized 方法控制对“对象的类成员变量”的访问:每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。(即锁定了调用这个方法的当前对象【this】)
(2)▪ synchronized块
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。
Java 为我们提供了更好的解决办法,那就是 synchronized 块。 块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。
synchronized 块:通过 synchronized关键字来声明synchronized 块,语法如下:
synchronized
(syncObject)
{
//允许访问控制的代码
}
3 死锁
解决:把锁的粒度加粗一点,
4 一道面试题
public class TestThread5 implements Runnable {
int b=100;
public synchronized void m1() throws InterruptedException {
b=1000;
Thread.sleep(5000);
System.out.println("b="+b);
}
public void m2()
{
b=2000;
System.out.println(b);
}
public void run()
{
try {
m1();
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
TestThread5 tt=new TestThread5();
Thread t=new Thread(tt);
t.start();
Thread.sleep(1000);//main睡1s,保证t已经执行到m1里了
tt.m2();
}
}
synchronized 方法控制对“对象的类成员变量”的访问:每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁方能执行,
m1()方法加了锁,所以他要执行的话,就要得到调用该方法对象的锁,m2没有加锁,它可以随便执行,所以上面这个例子,main开始执行,执行到t.start();,这时线程t开始执行,即调用run方法,即调用m1方法,修改b=1000,t开始sleep,这时main方法执行到tt.m2(),调用m2,修改b=2000。
所以这里对于都要修改的b,并没有任何保护措施,另一个线程也可以访问修改,不是光m1加了个锁就行的。
如果两个方法都修改了同样的值,为了保护共享数据,必须都应该加同步。
给m2()加了synchronized后,tt.m2()如果向调用,必须得获得调用该方法的对象的锁,即tt的锁,但是这时候m1()获得了锁,虽然他在休眠,但是m1()没有返回,这个锁是不放开的,所以m2()b必须得等到t线程的m1()执行完了,获得锁后才能执行。
没加锁的方法m2()别的线程(这里的main)还是可以自由访问,只是不能访问m1()里的这些句话了,保证只有一个线程进入m1的方法体里面,如果给m2也加了synchronized,则m2也不能进入了。
5 生产者-消费者问题
6 wait和sleep区别
wait时别的线程可以访问锁定对象(wait时候锁放开了)
---但是调用wait方法时必须锁定该对象
sleep时别的线程也不可以访问锁定多谢(sleep时候也抱着这把锁)