1、线程和进程
进程等于一个资源(即内存映射表)+多个指令执行序列,当切换进程的时候我们不仅要切换指令序列,而且要切换内存映射表,但是线程之间的切换只是在同一个内存映射表之间的指令切换(在java中切换pc,虚拟机栈,本地方法栈,不切换堆和方法区),线程既保留了并发的特点,又避免了线程切换的代价
2、start和run
调用start方法会新建一个线程,让这个新建的线程去调用run方法,而在当前线程中调用run方法,只是做为一个普通的方法调用,不能新建一个线程
3、怎么优雅的终止线程
不可用stop方法,因为stop方法太过暴力,强行把执行到一半的线程终止,可能会发生线程不一致的问题
可以用一个violatile变量来标记什么时候需要终止线程,让线程退出
4、wait和notify
当在一个对象实例上调用wait方法后,当前线程就会在这个对象上等待,比如线程A调用了obj.wait()方法,线程A就会进入等待状态,等待到别的线程调用了obj.notify方法为止,obj就成为了多个线程之间的有效通信手段。wait和notify究竟是怎么工作的呢,具体来说如果一个线程调用了object.wait(),那么它就会进入object对象的等待队列。当notify被调用时,它就会从这个等待队列中随机选择一个线程进行唤醒。这个选择是不公平的,并不是先等待的线程会优先被选择,完全随机。还需要强调的是,wait和notify并不能随便调用,它们必须包含在对应的synchronized语句中。相似的还有与ReentrantLock相关联的Condition的await方法,当线程被中断时,也能跳出等待。而且能被signal()方法唤醒,必须在相应的lock和unlock语句块中。Object.wait()和Thread.sleep()都可以让线程等待若干时间,除了wait可以被唤醒之外,wait释放目标对象的锁,但是sleep不会释放任何资源。
5、join和yield
public class JoinMain{
public volatile static int i = 0;
public static class AddThread extends Thread{
public void run(){
for(i = 0;i < 1000000;i++);
}
}
public static void main(String[] args){
AddThread at = new AddThread();
at.start();
at.join();
System.out.println(i);
}
}
join可以让调用线程在当前线程对象上进行等待,如果不调用join,主线程可能会输出0或者是得到的很小的数,因为AddThread还没开始执行,主线程就把i的值输出了,但使用join方法后,主线程就会等待AddThread执行完毕,并跟着AddThread一起走。
yield是指一旦调用,会使当前线程让出CPU,但让出CPU之后,还会进行CPU资源的争夺,能否再分配到就不一定了。
6、守护线程
守护线程是指在后台默默完成的一些系统性的服务,比如垃圾回收线程、JIT线程,与之相对应的是用户线程,用户进程就是系统的工作线程,当用户进程全部结束,这也意味着这个程序实际上无事可做了,当一个java应用内,只剩下守护线程时,Java虚拟机就会自然退出。比如下面的代码,当把t设置为守护线程时,当主线程结束后,程序就会自动结束,不然程序就会一直输出i am alive,需要注意的是setDaemon要设置在start()之前,不然t还是会被当成用户线程并且报异常IllegalThreadStateException
package JMM;
/**
* @author: surepeng
* @description:
* @date: 2019/07/24
**/
public class DaemonDemo {
public static class DaemonT extends Thread {
public void run() {
while (true) {
System.out.println("I am alive");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new DaemonT();
t.setDaemon(true);
t.start();
Thread.sleep(2000);
}
}
7、synchronized
关键词synchronized的作用是实现线程间的同步,它的工作是对同步的代码加锁,使得每一次只能有一个线程加入同步代码块,从而保证线程间的安全性
可以有多种用法:
指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
直接用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
对同一个对象instance加锁:
public class AccountingSync implements Runnable{
static AccountingSync instance = new AccountingSync();
static int i = 0;
public void run(){
for(int j = 0;j<1000000;j++){
synchronized(instance){
i++;
}
}
}
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();t2.start();
t1.join();//为了避免t1和t2都没执行完主线程就执行完了
t2.join();
System.out.println(i);
}
}
给同一个实例加锁,这里需要注意的是两个线程都指向同一个实例instance,如果是两个不同的对象,比如
Thread t1 = new Thread(new AccountingSync());
Thread t2 = new Thread(new AccountingSync());
其实就是两把锁,就会导致无法预计的异常
public class AccountingSync implements Runnable{
static AccountingSync instance = new AccountingSync();
static int i = 0;
public synchronized void increase(){
i++;
}
public void run(){
for(int j = 0;j<1000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();t2.start();
t1.join();//为了避免t1和t2都没执行完主线程就执行完了
t2.join();
System.out.println(i);
}
}
给同一个类加锁:
如果将上面的increase()方法变成static的,即使两个线程指向不同的Runnable对象,但由于方法快请求的是当前类的锁,而非当前实例,因此,线程间还是可以正常同步。
public class AccountingSync implements Runnable{
static AccountingSync instance = new AccountingSync();
static int i = 0;
public static synchronized void increase(){
i++;
}
public void run(){
for(int j = 0;j<1000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(new AccountingSync());
Thread t2 = new Thread(new AccountingSync());
t1.start();t2.start();
t1.join();//为了避免t1和t2都没执行完主线程就执行完了
t2.join();
System.out.println(i);
}
}