目录:
-
线程的四种开法 (三种方法的异同)
-
线程状态
-
join线程
-
守护线程
-
线程同步sychronized
-
volatile关键字
-
sychronized与volatile的区别
-
线程的通讯Communication
-
Lock
-
sychronized与lock的区别
-
线程的方法
线程的四种开法
Thread类 Runnable类 Callable类 线程池
Thread类
- 不能再从其他类继承
- 编写简单可直接操控线程
class A0 extends Thread
{
@Override
public void run()
{
for(int i = 0 ; i <= 10 ; i++)
{
System.out.println(Thread.currentThread().getName()+" " + i);
}
}
public static void main(String[] args) {
A0 a0 = new A0() ;
a0.start();
Runnable类 (更有优势)
- 可以将线程代码与线程数据分开形成清晰的模型
- 可以继承其他类
class B0 implements Runnable
{
@Override
public void run() {
for (int i = 1; i <=10 ; i++ )
{
System.out.println(Thread.currentThread().getName()+" " + i);
}
}
}
public static void main(String[] args) {
B0 b0 = new B0();
Thread t = new Thread(b0);
t.start();
Callable类
class C0 implements Callable<Integer>
{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0 ; i <= 100 ; i ++ )
{
sum = sum + i ;
System.out.println(Thread.currentThread().getName()+" " + i);
}
return sum;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
C0 c0 = new C0() ;
FutureTask<Integer> futureTask = new FutureTask(c0);
new Thread(futureTask).start();
C0 c1 = new C0() ;
FutureTask<Integer> futureTask1 = new FutureTask(c1);
new Thread(futureTask1).start();
System.out.println(futureTask.get());
System.out.println(futureTask1.get());
}
}
三种方法的异同:
- Callable()规定的方法是call() Runnable()规定的方法是run()
- Callable()执行方法后可返回值 Runnable()执行方法后不能返回值
- call()方法可以抛出异常 run()方法不能抛出异常
- 实现Callable() 或者Runnable()的类都是可以被其他其他线程执行的任务
- 运行Callable任务可以得到一个future对象 future表示异步计算的结果 提供了检查计算是否完成的方法 以等待计算完成 并检索计算结果
总结:
-
新的线程都保持空闲状态直到start()将其唤醒 在一个线程的生命周期中 start()只能被调用一次
-
可以使用stop()来永久结束线程 但是不推荐
-
JDK推荐使用Interrupt来中断线程 但是是有条件的 对线程的Interrupt是在sleep ,wait ,join ,这些状态时才起作用 这些状态下会一直检查是否中断 如果中断会抛出异常来结束进程
-
InterruptException 不是Interrupt ()抛出的
线程池创建线程
更容易执行线程的启动 执行 关闭 也可以很容易的使用线程池的特性
减少了创建和销毁线程的次数 每个工作线程都可以被重复利用 可执行多个任务
可以根据系统的承受能力 来调整线程池内的线程 防止因为消耗过多内存导致系统瘫痪(每个线程大约需要4MB)
public class D {
public static void main(String[] args) {
ExecutorService ex = Executors.newSingleThreadExecutor();
// ExecutorService ex = Executors.newCachedThreadPool(); 最大化 自动开
// ExecutorService ex = Executors.newFixedThreadPool(5); 限制大小
for (int i = 0 ; i <= 10 ; i++ )
{
ex.execute(new D0());
}
}
}
class D0 implements Runnable {
@Override
public void run() {
for (int i = 0 ; i <= 100 ; i++ )
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
线程状态
线程的生命周期与状态
线程可以一直执行下去 直到下面的某件事发生才停止
- 明确的从目标run()方法返回 run方法结束了
- 遇到一个无法捕获的运行时异常
- 调用了不推荐使用的stop()Interrupt()方法
如果上述内容没有发生 run()就会一直执行下去
调度与优先级
协同式线程调度
协同式线程调度,**线程的执行时间由线程本身控制。**协同式线程调度,线程执行时间由线程本身来控制,线程把自己的工作执行完之后,要主动通知系统切换到另外一个线程上。
优点: 实现简单,且切换操作对线程自己是可知的,没啥线程同步问题。
缺点: 线程执行时间不可控制,如果一个线程有问题,可能一直阻塞在那里。
抢占式线程调度
抢占式调度,每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。
Java中,Thread.yield()可以让出执行时间,但无法获取执行时间。
优点: 线程执行时间系统可控,也不会有一个线程导致整个进程阻塞。
Java提供一个线程调度器来监视和控制Runnable状态的线程。线程的调度策略采用抢占式,优先级高的线程比优先级低的线程优先执行。在优先级相同的情况下,按照“先到先服务”的原则。
每个Java程序都有一个默认的主线程,就是通过JVM启动的第一个线程。对于应用程序,主线程执行的是main()方法。对于Applet主线程是指浏览器加载并执行小应用程序的那一个线程。数值越大优先级越高
join线程
join线程执行后把后面所有线程和唤醒他的线程都阻塞 直到join线程完成后 自动唤醒其他线程 其他线程自动争抢
F0 f0 = new F0();
f0.setName("A");
f0.start();
try {
f0.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" " + i);
守护线程
守护其他所有线程 直到其他线程结束 自己默默执行完毕
创建一个能在应用程序中做一些简单的 周期性任务的后台程序 可以调用setDaemon()标记一个线程是守护线程
用户线程和守护线程的区别:
用户线程:java虚拟机在他所有的用户线程都离开后 java虚拟机才离开
守护线程:守护线程是用来服务用户线程的 如果没有其他用户线程在运行 那么就没有客服务对象 也就没有理由继续下去 守护线程会自动离开 一旦系统中都剩下守护线程 并不影响JVM的自动离开
线程同步
线程安全问题 银行取钱问题
private String name ;
private volatile Account account ;
private int money ;
public GetMoney(String name ,Account account ,int money)
{
this.name = name ;
this.account = account ;
this.money = money ;
}
@Override
public void run()
{
synchronized(this.account){
if (this.money <= this.account.getFund() )
{
this.account.setFund(this.account.getFund()-this.money);
System.out.println(this.name +"取出"+ " "+ this.money);
}else
{
System.out.println("账户余额不足");
}
}
public class Account {
private int fund;
private String no;
public Account(int fund, String no) {
this.fund = fund;
this.no = no;
}
public Account(){}
public int getFund() {
return fund;
}
public String getNo() {
return no;
}
public void setFund(int fund) {
this.fund = fund;
}
public void setNo(String no) {
this.no = no;
}
}
public static void main(String[] args) {
Account a = new Account(800,"1");
GetMoney g1 = new GetMoney("A",a,800);
GetMoney g2 = new GetMoney("B",a,800);
GetMoney g3 = new GetMoney("C",a,800);
g1.start();
g2.start();
g3.start();
volatile关键字
保证内存的可见性 每次访问变量时都会进行一次刷新 因此每次访问都是主存中的最新版本
防止指令重排 不保证原子性
当满足以下条件后才使用volatile:
该变量没有包含在具有其他变量的不变式中
对变量的写入操作不依赖变量当前值
由于使用volatile屏蔽掉了JVM中必要的代码优化 所以在效率上比较低
在两个或者更多线程访问成员变量上使用volatile 当要访问的变量已经在sychronized代码块中的时候 或者是为常量时 没必要再写volatile
sychronized与volatile的区别:
- volatile仅能使用在变量级别 sychronized可以使用在变量 方法级别
- volatile仅能实现变量的修改可见性 sychronized 则可以保证变量修改的可见性和原子性
- volatile本质是告诉JVM当前变量在寄存器中的值是不确定的 需要从主存中读取 sychronized则是锁定当前变量 只有当前线程可以访问当前变量 其他线程被阻塞住
- volatile不会造成线程阻塞 sychronized可能造成线程阻塞
- 当一个域的值依赖于之前的值 volatile就无法工作 如i++等 如果某个域的值被其他域值限制那么volatile也无法工作
- 使用volatile而不使用 sychronized的唯一安全情况就是类中只有一个可变的域
- volatile不会进行加锁
- sychronized比volatile更安全
- volatile无法同时保证内存可见性和原则性
线程的通讯Communication
方法涉及到wait() notify()notifyAll()
public void saveMoney(int money){
synchronized (this){
if(flag)//true:不能存,只能取
{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else
{
this.fund += money ;
System.out.println("存"+ money+ "余额"+this.fund);
flag = true ;
notifyAll();
}
}
Lock
ReentrantLock有嗅探锁定 多路分支等功能
嗅探锁定 是指在获取锁得时候如果被其他线程获取到 ReentrantLock可以进行指定等待时间获取锁
多路分支通知 是指线程发生wait时 线程可以选择注册在不同的监测器Condition对象上 在适当的时候可以指定选择监测器Condition对象上的线程进行signal通知
sychronized与lock的区别
- sychronized(隐式锁):在需要同步的对象中加入此控制 sychronized可以加在方法上 也可以加在特定代码块中 括号中表示需要锁的对象
- lock(显示锁):一般使用ReentrantLock类作为锁 需要显示锁定起始位置和终止位置 多个线程中必须使用 ReentrantLock 类作为对象才能保证锁的生效 在加锁和解锁处要通过lock()和unlock()指出 一般会在finally()块中写unlock()防止死锁
- sychronized是托管给JVM执行 而lock是java写的控制锁的代码
- sychronized采用的是CPU悲观锁机制 即为线程获得独占锁 独占锁意味着其他线程只能依靠阻塞来等待线程释放锁
- lock采用的是乐观锁 乐观锁是每次不加锁 而是假设每次没有冲突去完成某项操作 如果因为冲突失败就重试 直到成功为止 乐观锁的实现机制是CAS操作