最近一直在复习java线程相关的知识,以前零零散散的看过,这次自己试着总结一下:
1线程和进程的基本概念以及特点?
--进程是现代分时操作系统的工作单元,通常一个程序对应一个进程;每个进程都有自己的独立资源和自己的私有地址空间,在没有经过进程允许的情况下,一个进程不可以访问其他进程的地址空间。
--线程被称作轻量级进程,是进程执行的基本单元;一个进程可以拥有多个线程,一个线程必须有一个父进程;线程拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其它线程共享该进程拥有的全部资源;线程是独立运行的,他不知道有其它线程的存在。
《现代操作系统》
2java创建线程的方式
--Java创建线程通常有以下方式:
1继承Thread类 2实现Runnable接口 3使用Callable和Future接口创建线程 4使用线程池创建线程
package xiancheng;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/*
使用Callable和future接口创建多线程,先将实现Callable接口的类包装成FutureTask对象,调用Thread类的方法即可
*/
public class ThirdThread {
public static void main(String[] args){
Callable<Integer> myCallable=new MyCallable();
FutureTask<Integer> ft=new FutureTask<Integer>(myCallable);
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==30){
Thread thread=new Thread(ft);
thread.start();
}
}
System.out.println("the main process has ended");
try{
//return the value of call() method
int sum=ft.get();
System.out.println("sum="+sum);
}catch (InterruptedException e){
e.printStackTrace();
}catch (ExecutionException e){
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer>{
private int i;
@Override
public Integer call() throws Exception {
int sum=0;
for(;i<100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
sum+=i;
}
return sum;
}
}
3java多线程的阻塞状态与线程控制
--join()让一个线程等待另一个线程完成才继续执行。如A线程线程执行体中调用B线程的join()方法,则A线程被阻塞,直到B线程执行完为止,A才能得以继续执行。
--sleep()让当前的正在执行的线程暂停指定的时间,并进入阻塞状态。在其睡眠的时间段内,该线程由于不是处于就绪状态,因此不会得到执行的机会。即使此时系统中没有任何其他可执行的线程,出于sleep()中的线程也不会执行。因此sleep()方法常用来暂停线程执行。
--setPriority()每个线程在执行时都具有一定的优先级,优先级高的线程具有较多的执行机会。每个线程默认的优先级都与创建它的线程的优先级相同。main线程默认具有普通优先级。
--yield()线程让步,yield()方法还与线程优先级有关,当某个线程调用yiled()方法从运行状态转换到就绪状态后,CPU从就绪状态线程队列中只会选择与该线程优先级相同或优先级更高的线程去执行。
4后台线程
--后台线程主要是为其它线程提供服务,如jvm中的垃圾回收线程;当后台线程所服务的线程全部死亡时,后台线程会自动死亡,可以采用setDaemon方法将某个线程设置为守护线程。
5线程同步的方法
--采用synchronized关键字修饰方法(代码块),使其成为同步方法(代码块);synchronized关键字不能被继承
--采用同步锁lock,显示定义同步锁对象来实现同步,比较常用的是ReentrantLock(可重入锁)。
package xiancheng.synchronizedTest;
public class Ticket implements Runnable {
private int num;
private boolean flag=true;
public Ticket(int num){
this.num=num;
}
@Override
public void run(){
while (flag){
ticket();
}
}
private void ticket(){
synchronized (this){
if(num<=0){
flag=false;
return;
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票序列号"+num--);
}
}
public static void main(String[] args) {
Ticket ticket=new Ticket(5);
Thread thread1=new Thread(ticket,"窗口1");
Thread thread2=new Thread(ticket,"窗口2");
Thread thread3=new Thread(ticket,"窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
package xiancheng.Lock;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest implements Runnable {
private Lock lock=new ReentrantLock();
private List<Integer> list=new ArrayList<>();
@Override
public void run(){
lock.lock();
try {
for(int i=0;i<10;i++){
list.add(i);
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
System.out.println(Thread.currentThread().getName()+"释放了锁");
}
}
public static void main(String[] args) {
LockTest lt=new LockTest();
Thread t1=new Thread(lt,"first");
Thread t2=new Thread(lt,"second");
Thread t3=new Thread(lt,"third");
t1.start();
t2.start();
t3.start();
}
}
6线程类的常用方法
--sleep(): 强迫一个线程睡眠N毫秒。
isAlive(): 判断一个线程是否存活。
join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
isDaemon(): 一个线程是否为守护线程。
setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
setName(): 为线程设置一个名称。
wait(): 强迫一个线程等待。
notify(): 通知一个线程继续运行。
setPriority(): 设置一个线程的优先级。
7synchronized关键字的细节
--synchronized锁住的是类的实例对象,而不是代码。对于非static的synchronized方法,锁的就是对象本身也就是this。
当synchronized锁住一个对象后,别的线程如果也想拿到这个对象的锁,就必须等待这个线程执行完成释放锁,才能再次给对象 加锁,这样才达到线程同步的目的。但是如果有多个实例对象,则可以通过未被锁住的实例对象访问synchronized修饰的方法,这个时候可以锁住这个类的class对象。
package xiancheng.synchronizedTest;
public class IncrementNumber implements Runnable {
private static int i=0;
@Override
public synchronized void run(){
for(int k=0;k<1000;k++){
i++;
}
}
public static void main(String[] args) throws InterruptedException {
IncrementNumber in=new IncrementNumber();
Thread t1=new Thread(in);
Thread t2=new Thread(in);
Thread t3=new Thread(new IncrementNumber());
t1.start();
t2.start();
t3.start();
t1.join(10);
t2.join(10);
System.out.println(i);
}
}
8线程通信
--当使用synchronized来保证同步,因为该类的默认实例就是同步监听器,可以借助object类的wait,notify,notifyall方法来实现线程通信。
--当使用lock同步锁来保证同步时,此时没有隐式的同步监听器,所以需要通过condition类的实例对象来实现线程通信,对应的方法为await,signal,signalall。
--使用阻塞队列BlockingQuene实现线程通信,BlockingQuene有一个特征,当向其中放入元素时,若队列已满,则会阻塞线程;当从其中取出元素时,若队列为空,则线程会阻塞,对应的方法分别为put和take。
9线程池
--系统开始新的线程需要与操作系统交互,有较大的开销,可以通过建立线程池避免这种频繁的开销。在java中,可以通过Executors类的静态方法来创建对象,该对象代表一个线程池。
package xiancheng.ThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//创建线程池的四种方式
public class ThreadPool {
public static void main(String[] args) {
//创建固定大小的线程池,需要手动释放资源
ExecutorService fixPool= Executors.newFixedThreadPool(10);
//创建缓存线程池
ExecutorService cachePool=Executors.newCachedThreadPool();
//创建单例线程池,需要手动释放资源
ExecutorService singlePool=Executors.newSingleThreadExecutor();
//创建周期线程池
ExecutorService schedulePool=Executors.newScheduledThreadPool(10);
}
private static Runnable getThread(final int i){
return new Runnable() {
@Override
public void run() {
System.out.println(i);
}
};
}
}
10线程的相关类
--ThreadLocal(线程局部变量),该类会为每个线程提供一个变量值的副本,使每个线程都可以独立地改变自己的副本,从而保证线程安全;ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本。ThreadLocal在Spring中发挥着巨大的作用,在管理Request作用域中的Bean、事务管理、任务调度、AOP等模块都出现了它的身影。
package ConcurrencyInJava.chapter01;
public class ThreadLocalTest {
static ThreadLocal<String> localVariable=new ThreadLocal<>();
static void print(String str){
System.out.println(str+":"+localVariable.get());
localVariable.remove();
}
public static void main(String[] args) {
Thread threadOne=new Thread(new Runnable() {
@Override
public void run() {
localVariable.set("threadOne local variable");
print("threadOne");
System.out.println("threadOne remove after"+":"+localVariable.get());
}
});
Thread threadTwo=new Thread(new Runnable() {
@Override
public void run() {
localVariable.set("threadTwo local variable");
print("threadTwo");
System.out.println("threadTwo remove after"+":"+localVariable.get());
}
});
threadOne.start();
threadTwo.start();
}
}
10synchronized与lock的区别
--synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中;在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
--ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断。
可以参考这篇博客https://www.cnblogs.com/QQParadise/articles/5059824.html
11volatile关键字的作用
--1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序:当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。但应极大注意,volatile无法保证操作的原子性,在使用的时候要谨慎。
package xiancheng.volatileTest;
//针对volatile关键的操作必须是原子性的,否则不能保证数据一致性
public class VolatileTest implements Runnable{
private static volatile int i=1;
@Override
public void run(){
i++;
}
public static void main(String[] args) throws InterruptedException {
VolatileTest vt=new VolatileTest();
Thread t1=new Thread(vt);
t1.start();
Thread.currentThread().join(1000);
System.out.println(i);
}
}
package xiancheng.AtomicTest;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicDemo implements Runnable {
private AtomicInteger serialNumber=new AtomicInteger();
@Override
public void run(){
try {
Thread.sleep(200);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+getSerialNumber());
}
public int getSerialNumber(){
return serialNumber.getAndIncrement();
}
}
《java并发编程实战》
可以参考海子的博客https://www.cnblogs.com/dolphin0520/p/3920373.html
12volatile和synchronized关键字的区别
--1 粒度不同,前者针对变量 ,后者锁对象和类
2 syn阻塞,volatile线程不阻塞
3 syn保证三大特性,volatile不保证原子性
4 syn编译器优化,volatile不优化