Java多线程(一)线程基础、线程间的共享和协作

通过JVM给我们的入口,可以知道运行期间有几个线程:

ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo threadInfo : threadInfos){
System.out.println(threadInfo);
}


一、启动线程的三种方式:
1.继承 Thread
2.实现Runnable接口
3.实现Callable接口(有返回值)

public class NewThread {

private static class UseRun implements Runnable{

@Override
public void run() {
System.out.println("I am implements Runnable");
}

}

private static class UseCall implements Callable<String>{

@Override
public String call() throws Exception {
System.out.println("I am implements Callable");
return "CallResult";
}
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
UseRun useRun = new UseRun();
//Runnable接口能够直接作为Thread的构造参数
Thread thread1 = new Thread(useRun);
UseCall useCall = new UseCall();
//Callable接口不能作为Thread的构造参数
//需要通过FutureTask类的转换,将其转换为Runnable接口
//因为FutureTask实现了Runnable接口
FutureTask<String> futureTask = new FutureTask<>(useCall);
Thread thread2 = new Thread(futureTask);
thread2.start();
//通过futureTask的get方法得到返回值
     //get方法是一个阻塞方法,一直等到有返回值后才会继续往下走
System.out.println(futureTask.get());
}

}


二、停止线程的三种方式:
1.自然执行完成死亡
2.在没有对异常进行处理时抛出异常会中止线程
3.早期stop()等因为过于强势,无法保证线程资源释放,容易导致死锁的问题‘
4.interrupt():中断一个线程,将中断标志位置为true
  isinterrupted():判定当前线程是否处于中断状态
  static方法interrupted():判定当前线程是否处于中断状态,同时将中断标志位改为False,这是2和3的最主要的区别
由于Java中线程是协作式的,interrupt方法只会通知线程应该中断了,并不会强制中断
目的:为了让每个线程有时间释放占有的资源
在实际操作过程中是要自己轮询标志位进行判断的,如果不对标志位进行判断,则不会有任何影响。

问:但是interrupt()和isinterrupted方法都是Thread类里面的方法,Runnable接口怎么办?
  判断isinterrupted的时候用Thread.currentThread.isinterrupted()方法就可以了

package thread;
/*
如何安全地中断线程
*/
public class EndThread {

private static class UseThread extends Thread{
public UseThread(String name) {
super(name);
}

@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (!isInterrupted()){
System.out.println(threadName);
}
System.out.println(isInterrupted()+"中断标志位");
}
}

private static class UseThread2 extends Thread{
public UseThread2(String name) {
super(name);
}

@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (!Thread.currentThread().isInterrupted()){
System.out.println(threadName);
}
System.out.println(Thread.currentThread().isInterrupted()+"中断标志位");
}
}


private static class UseThread3 extends Thread{
public UseThread3(String name) {
super(name);
}

@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (!isInterrupted()){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//这里打印的是false
System.out.println(isInterrupted()+"中断标志位");
            //自行interrupt一次才会中断
            interrupt();
e.printStackTrace();
}
//此时线程并没有中断
System.out.println("***");
}
System.out.println(isInterrupted()+"中断标志位while结束后");
}
}

public static void main(String[] args) throws InterruptedException {
Thread endThread = new UseThread3("endThread");
endThread.start();
Thread.sleep(10);
endThread.interrupt();
}

}

注意:当方法抛出InterruptedException异常的时候,线程的中断标志位会复位成False,只有在catch语句中再执行一次interrupt方法
  (再次中断)才会中断。InterruptedException所有阻塞方法都会抛出异常
如果说用自己定义的标志位,阻塞时的判断就会不及时,不建议使用

 

 

 yield():用的少,示意线程让出时间片,然后和其他线程一起抢,可能再次抢到。

 

三、其他知识:
·设置线程优先级1-10:Thread.setPriority(x);
·守护线程:
  一般来说主线程结束后如果子线程还没处理完会继续运行
  守护线程和主线程共死,如GC线程
  特别注意:
    Thread.setDaemon(true);//一定要在start()方法之前设置
    try//finally语句块中的语句在守护线程里面不一定会执行


四、线程间的共享:
·synchronized关键字(内置锁):
  确保任一时刻只能由一个线程处于同步方法或同步块中
  锁的实际上是具体的一个对象。改变对象的标志位指向某一线程。
  对象锁:在普通方法上加,或者synchronized(this){}也是对象锁
  类锁 :在static方法上加,或者
synchronized(xxx.class){}(锁的是class对象)
·volatile关键字:
  每次获取值的时候都要从主内存中读取
  每次修改完之后都要把值刷回主内存
  保证了可见性,轻量同步机制,不能保证原子性()。
  最大的用处是在一个线程写,多个线程读时最常用

·ThreadLocal线程变量:
  确保每个线程只使用自己的那一个拷贝,主要用在连接池,保持每个线程的连接,内部用Map维护
package thread;

public class UseThreadLocal {
    //例化了一个ThreadLocal对象。我们只需要实例化对象一次,并且也不需要知道它是被哪个线程实例化。
      虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程却只能访问到自己通过调用ThreadLocal的
      set()方法设置的值。即使是两个不同的线程在同一个ThreadLocal对象上设置了不同的值,他们仍然无法访问到对方的值。

static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
//设置初始值
@Override
protected Integer initialValue() {
return 1;
}
};


//运行三个线程
public void StartThreadArray(){
Thread[] runs = new Thread[3];
for (int i = 0; i < runs.length; i++) {
runs[i] = new Thread(new TestThread(i));
}
for (int i = 0; i < runs.length; i++) {
runs[i].start();
}
}

//测试线程,将ThreadLocal变量的值变化,并写回,看看线程之间是否会相互影响
public static class TestThread implements Runnable{

int id;

public TestThread(int id) {
this.id = id;
}

@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"start");
Integer s = threadLocal.get();
s = s+id;
threadLocal.set(s);
System.out.println(Thread.currentThread().getName()+"当前"+threadLocal.get());
}
}

public static void main(String[] args) {
UseThreadLocal test = new UseThreadLocal();
test.StartThreadArray();
}

}
五、线程间协作
·等待和通知的标准范式
  wait()、notify/notifyall()是Object中的方法。
  等待方:
    1、获取对象的锁
    2、循环里判断条件是否满足,不满足调用wait方法
    3、条件满足执行业务逻辑
  通知方:
    1、获取对象的锁
    2、改变条件
    3、通知所有等待该对象的线程
  尽量使用notifyall,防止发生信号丢失情况
  例子:Express类
package thread.waitNotify;

public class Express {

public final static String CITY = "shanghai";
private int km;//里程数
private String site;//快递到达地点

public Express() {
}

public Express(int km, String site) {
this.km = km;
this.site = site;
}

//变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理
public synchronized void changeSite(){
this.site="beijing";
notifyAll();
}

//变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理
public synchronized void changeKm(){
this.km = 101;
notifyAll();
}

public synchronized void waitKm(){
while (this.km<=100){
try {
wait();
System.out.println("check km thread"+
Thread.currentThread().getId()+
"is notified");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("the km is"+this.km);
}

public synchronized void waitSite(){
while (this.site.equals(CITY)){
try {
wait();
System.out.println("check site thread"+
Thread.currentThread().getId()+
"is notified");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("the site is"+this.site);
}
}
测试代码:
package thread.waitNotify;

import java.util.concurrent.TimeUnit;

public class TestWN {
private static Express express = new Express(0,Express.CITY);

//检查里程数的线程,不满足条件就一直等待
private static class CheckKm extends Thread{
@Override
public void run() {
express.waitKm();
}
}

//检查地点的线程,不满足条件就一直等待
private static class CheckSite extends Thread{
@Override
public void run() {
express.waitSite();
}
}

public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new CheckKm());
Thread thread2 = new Thread(new CheckSite());
thread1.start();
thread2.start();
TimeUnit.MILLISECONDS.sleep(100);
express.changeKm();
}
}
·等待超时模式(连接池的实现):
  假设等待时间为T,当前时间now+T以后超时
  long overTime = now+T;
  long remain = T;//等待的持续时间
  while(result 不满足条件 && remain>0){
    wait(remain);
    remain = overTime-now;//剩下的持续时间
  }
  return result;
数据库连接池模拟:
  //在mills时间内拿不到数据库连接,返回一个null
  public Connection fetchConn(long mills){
    synchronized(poolList){
      long overTime = System.currentTime+mills;
      long remian = mills;
      while(poolList.isEmpty && remian>0){
        poolList.wait(remain);
        remian = overtime - now();
      }
      Connection result = null;
      if(!poolList.isEmpty()){
        result = poolList.removeFirst();
      }
      return result;
    }
  }
//放回数据库连接
public void releaseConn{
  synchronized(poolList){
    poolList.addLast(conn);
    poolList.notifyAll();
  }
}


·join()方法:
  面试点:线程A执行了线程B的join方法,那么线程A就必须要等待B执行完成了之后才能继续执行(请美女排你前面插队)


六、调用yield、sleep、wait、notify等方法对锁有何影响
线程在执行yield以后,持有的锁是不是放的,cpu仍可能调度这个线程
sleep方法也不会释放锁
在调用wait方法之前必须要持有锁,在调用wait方法之后这个锁就会被释放,当wait方法返回的时候线程会重新持有锁
notify在调用之前也必须要持有锁,在同步代码块跑完之后才会释放锁

posted @ 2020-04-28 23:30  莫西123  阅读(241)  评论(0)    收藏  举报