精华内容
下载资源
问答
  • 点击上方☝,轻松关注!及时获取有趣有料的技术文章本文讲一下Java线程池中创建 ThreadFactory 设置线程名称的三种方式。设置线程名称是很重要的,如果你没有设置过,说明你还“涩...

    点击上方☝,轻松关注!及时获取有趣有料的技术文章

    本文讲一下Java线程池中创建 ThreadFactory 设置线程名称的三种方式。设置线程名称是很重要的,如果你没有设置过,说明你还“涩世”不深,这里面的坑还不曾踩过,而我 在坑里进去然后坑里出来,被坑的那是一个相当的爽啊~

    为了让不重蹈我的覆辙,为了未来不说起来都是满眼含着“热泪”,还是看下如何设置,顺便也设置设置。

    概览

    第一种 CustomizableThreadFactory

    Spring 框架提供的 CustomizableThreadFactory

    ThreadFactory springThreadFactory = new CustomizableThreadFactory("springThread-pool-");
    	
    
    ExecutorService exec = new ThreadPoolExecutor(1, 1,
    		0L, TimeUnit.MILLISECONDS,
    		new LinkedBlockingQueue<Runnable>(10),springThreadFactory);
    exec.submit(() -> {
    	logger.info("--记忆中的颜色是什么颜色---");
    });
    
    

    第二种 ThreadFactoryBuilder

    Google guava 工具类 提供的 ThreadFactoryBuilder ,使用链式方法创建。

    ThreadFactory guavaThreadFactory = new ThreadFactoryBuilder().setNameFormat("retryClient-pool-").build();
    
    
    ExecutorService exec = new ThreadPoolExecutor(1, 1,
    		0L, TimeUnit.MILLISECONDS,
    		new LinkedBlockingQueue<Runnable>(10),springThreadFactory);
    exec.submit(() -> {
    	logger.info("--记忆中的颜色是什么颜色---");
    });
    

    第三种 BasicThreadFactory

    Apache commons-lang3 提供的 BasicThreadFactory.

    ThreadFactory basicThreadFactory = new BasicThreadFactory.Builder()
    		.namingPattern("basicThreadFactory-").build();
    
    ExecutorService exec = new ThreadPoolExecutor(1, 1,
    		0L, TimeUnit.MILLISECONDS,
    		new LinkedBlockingQueue<Runnable>(10),springThreadFactory);
    exec.submit(() -> {
    	logger.info("--记忆中的颜色是什么颜色---");
    });
    
    

    总结

    最终本质都是 给 java.lang.Thread#name 设置名称,详情源码感兴趣的可以自行查看。

    final Thread thread = new Thread();
    thread.setName(name);
    
    

    本文完~

    推荐预阅读

    推荐一款Java音频视频编码器,很赞~

    2020年Java程序员20个超强练手项目

    一个技术人珍藏的武学秘籍

    点亮在看

    展开全文
  • java线程池

    2017-12-07 10:11:00
    ExecutorService executor = Executors.newFixedThreadPool(10); executor.shutdown(); executor.shutdownNow(); Thread.currentThread().getName(); //获取线程名称 //没有返回值 execu
    ExecutorService executor = Executors.newFixedThreadPool(10);
    
            executor.shutdown();
            executor.shutdownNow();
            Thread.currentThread().getName(); //获取线程名称
            //没有返回值
            executor.submit(new Runnable() {
    
                @Override
                public void run() {
    
    
                }
            });
            executor.execute(new Runnable() {
    
                @Override
                public void run() {
    
    
                }
            });
    
            Future<T> submit = executor.submit(new Callable<T>() {
    
                @Override
                public T call() throws Exception {
    
                    return null;
                }
            }); // 有返回值
            submit.get(); //获取返回值
        }

    executor.shutdown()
    executor.shutdownnow()

    package run;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    import service.CrawlerPostgraduate;
    
    public class Start {
        public static void main(String[] args) {
            // 初始化多个需要爬的类
            List<Callable<String>> tasks = new ArrayList<Callable<String>>();
            CrawlerPostgraduate cp = new CrawlerPostgraduate();
            tasks.add(cp);
            // 线程池
            ExecutorService executor = Executors.newFixedThreadPool(10);
            List<Future<String>> invokeAll = null;
            try {
                invokeAll = executor.invokeAll(tasks);
            } catch (InterruptedException e) {
    
                e.printStackTrace();
            }
    
            for (Future<String> future : invokeAll) {
                try {
                    System.out.println(future.get());
                } catch (InterruptedException e) {
    
                    e.printStackTrace();
                } catch (ExecutionException e) {
    
                    e.printStackTrace();
                }
            }
            executor.shutdown();
        }
    
    }
    
    展开全文
  • 目录创建线程的3种方式:1、方式一:继承Thread类,重写run()...设置或获取多线程的线程名称的方法3. 线程休眠------sleep()方法4. 线程让步------yield()方法5. 等待线程终止------join()方法6. 线程停止7.线程等待

    线程

    线程是CPU调度的最小单位,也叫轻量级进程LWP(light weight Process)
    两种线程模型:

    • 用户级线程(ULT):用户程序实现,不依赖操作系统核心,用户提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/核心态切换,速度快。内核对ULT无感知,线程阻塞则进程(包括它的所有线程)阻塞。
    • 内核级线程(KLT):系统内核管理线程(KLT),内核保存线程的状态和上下文信息,线程阻塞不会引起进程阻塞。在多出力器系统上,多线程在多处理器上并行运行。线程的创建、调度和管理由内核完成,效率比ULT要慢,比进程操作快。

    创建线程的3种方式:

    参考:https://blog.csdn.net/qq_35275233/article/details/87893337
    https://blog.csdn.net/tongxuexie/article/details/80142638

    1. 继承Thread类,重写run()方法
    2. 实现Runnable接口,实现run()方法
    3. 实现Callable接口,并实现其call()方法

    1、方式一:继承Thread类,重写run()方法(extends)

    (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体(线程体)。
    (2)创建Thread子类的实例,即创建了线程对象。
    (3)调用线程对象的start()方法来启动该线程。

      
    public class FirstThreadTest extends Thread{  
        int i = 0;  
        //重写run方法,run方法的方法体就是现场执行体  
        public void run()  
        {  
            for(;i<100;i++){  
            log.info(getName()+"  "+i);  
            }  
        }  
     
        public static void main(String[] args)  
        {  
            for(int i = 0;i< 100;i++)  
            {  
                log.info(Thread.currentThread().getName()+"  : "+i);  
                if(i==20)  
                {  
                    new FirstThreadTest().start();  
                    new FirstThreadTest().start();  
                }  
            }  
        }  
    } 
    

    上述代码中Thread.currentThread()方法返回当前正在执行的线程对象。GetName()方法返回调用该方法的线程的名字。
    或者还有其他的例子:

    class MyThread extends Thread{
    	@Override
    	public void run() {
    		System.out.println("自己创建的线程");
    	}
    }
    public class Genericity {
    	public static void main(String[] args) {
    		//实例化一个对象
    		MyThread myThread=new MyThread();
    		//调用Thread类的start()方法
    		myThread.start();
    		//在main方法中打印一条语句
    		System.out.println("main方法");
    	}
    }
    
    运行结果为:
    main方法
    自己创建的线程
    

    2、方式二:实现Runnable接口,实现run()方法

    (1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
    (2)创建 Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
    (3)调用线程对象的start()方法来启动该线程。

    public class RunnableThreadTest implements Runnable  
    {  
      
        private int i;  
        public void run()  
        {  
            for(i = 0;i <100;i++)  
            {  
               log.info(Thread.currentThread().getName()+" "+i);  
            }  
        }  
     
        public static void main(String[] args)  
        {  
            for(int i = 0;i < 100;i++)  
            {  
                log.info(Thread.currentThread().getName()+" "+i);  
                if(i==20)  
                {  
                    RunnableThreadTest runner= new RunnableThreadTest();  
                    new Thread(runner,"新线程1").start();  
                    new Thread(runner,"新线程2").start();  
                }  
            }  
        }   
    }  
    

    线程的执行流程很简单,当执行代码start()时,就会执行对象中重写的void run()方法,该方法执行完成后,线程就消亡了。
    或者:

    public class RunnableTest implements Runnable{
        @Override
        public void run() {
            System.out.println("thread is running!");
        }
        public static void main(String[] args) {
            Thread t1 = new Thread(new RunnableTest());
            t1.start();
        }
    }
    

    匿名内部类的两种写法

    public class App {
        public static void main(String[] args){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("thread1 is running!");
                }
            }){}.start();
    
            new Thread(){
                @Override
                public void run(){
                    System.out.println("thread2 is running!");
                }
            }.start();
        }
    }
    

    方式三:实现Callable接口,并实现其call()方法

    (1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

    public interface Callable
    {
      V call() throws Exception;
    }
    

    (2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方
    法的返回值。(FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。)
    (3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
    (4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

    public class CallableThreadTest implements Callable<Integer>  
    {  
      
        public static void main(String[] args)  
        {  
            CallableThreadTest ctt = new CallableThreadTest();  
            FutureTask<Integer> ft = new FutureTask<>(ctt);  
            for(int i = 0;i < 100;i++)  
            {  
                log.info(Thread.currentThread().getName()+" 的循环变量i的值"+i);  
                if(i==20)  
                {  
                    new Thread(ft,"有返回值的线程").start();  
                }  
            }  
            try  
            {  
                log.info("子线程的返回值:"+ft.get());  
            } catch (InterruptedException e)  
            {  
                e.printStackTrace();  
            } catch (ExecutionException e)  
            {  
                e.printStackTrace();  
            }  
      
        }  
      
        @Override  
        public Integer call() throws Exception  
        {  
            int i = 0;  
            for(;i<100;i++)  
            {  
                log.info(Thread.currentThread().getName()+" "+i);  
            }  
            return i;  
        }  
      
    }  
    

    其他的例子:

    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
     
    //1.定义一个类MyThread实现Callable接口,从而覆写call()方法
    class MyThread implements Callable<String>{
    	@Override
    	public String call() throws Exception {
    		return "Callable接口创建线程";
    	}
    }
    public class Genericity {
    	public static void main(String[] args) throws InterruptedException, ExecutionException {
    		//2.利用MyThread类实例化Callable接口的对象
    		Callable callable=new MyThread();
    		//3.利用FutureTask类的构造方法public  FutureTask(Claaable<V> callable)
    		//将Callable接口的对象传给FutureTask类
    		FutureTask task=new FutureTask(callable);
    		//4.将FutureTask类的对象隐式地向上转型
    		//从而作为Thread类的public Thread(Runnable runnable)构造方法的参数
    		Thread thread=new Thread(task);
    		//5.调用Thread类的start()方法
    		thread.start();
    		//FutureTask的get()方法用于获取FutureTask的call()方法的返回值,为了取得线程的执行结果
    		System.out.println(task.get());
    	}
    }
    

    (1)自定义一个类MyThread,实现Callable接口并覆写Callable接口的call()方法
    (2)利用MyThread类实例化Callable接口的对象
    (3)利用FutureTask类的构造方法public FutureTask(Claaable callable),将Callable接口的对象传给FutureTask类
    (4)将FutureTask类的对象隐式地向上转型,从而作为Thread类的public Thread(Runnable runnable)构造方法的参数
    (5)这样就建立了Callable接口与Thread类之间的关系,再调用Thread类的start()方法

    其中1与2哪一种方式比较好呢?
    Java不支持类的多重继承,但允许你调用多个接口。所以用实现Runnable接口比较好

    实现Runnable接口和Callable接口的区别

    (1)Callable规定的方法是call(),Runnable规定的方法是run().
    (2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
    (3)call方法可以抛出异常,run方法不可以
    (4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

    线程的thread.start()和thread.run()方法有什么区别?

    **start()方法被用来启动新创建的线程,**使该被创建的线程状态变为可运行状态。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动start()方法才会启动新线程。如果我们调用了Thread的run()方法,它的行为就会和普通的方法一样,直接运行run()方法。为了在新的线程中执行我们的代码,必须使用Thread.start()方法。
    new 一个 Thread,线程进入了新建状态;调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,那么代码还是同步执行的,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码。所以这并不是多线程工作。

    线程状态:

    在这里插入图片描述

    1. 线程的五大状态及其转换

    线程的五大状态分别为:

    1. 新建状态(New)
    2. 可运行状态(Runnable)
    3. 运行状态(Running)
    4. 阻塞状态(Blocked)
    5. 死亡状态(Dead)
      (1)新建状态:即单纯地创建一个线程,创建线程有三种方式
      (2)可运行状态:在创建了线程之后,调用Thread类的start()方法来启动一个线程,即表示线程进入可运行状态!
      (3)运行状态:当线程获得CPU时间,线程才从就绪状态进入到运行状态!
      (4)阻塞状态:线程进入运行状态后,可能由于多种原因让线程进入阻塞状态,如:调用sleep()方法让线程睡眠,调用wait()方法让线程等待,调用join()方法、suspend()方法(它现已被弃用!)以及阻塞式IO方法。
      (5)死亡状态:run()方法的正常退出就让线程进入到死亡状态,还有当一个异常未被捕获而终止了run()方法的执行也将进入到死亡状态!

    2.设置或获取多线程的线程名称的方法

    由于在一个进程中可能有多个线程,而多线程的运行状态又是不确定的,即不知道在多线程中当前执行的线程是哪个线程,所以在多线程操作中需要有一个明确的标识符标识出当前线程对象的信息,这个信息往往通过线程的名称来描述。在Thread类中提供了一些设置或获取线程名称的方法:
    (1)创建线程时设置线程名称:

    public Thread(Runnable target,String name)
    

    (2)设置线程名称的普通方法:

    public final synchronized void setName(String name)
    

    (3)取得线程名称的普通方法:

    public final String getName()
    

    下面将在代码中使用以上方法,作为演示:

    class MyThread implements Runnable{
    	@Override
    	public void run() {
    		for(int i=0;i<5;i++)
    		{
    			//currentThread()方法用于取得当前正在JVM中运行的线程
    			//使用getName()方法,用于获取线程的名称
    			System.out.println("当前线程:"+Thread.currentThread().getName()+"-----i="+i);
    		}
    	}
    }
    public class Test1 {
    	public static void main(String[] args){
    		//创建线程对象thread1且没有设置线程名称
    		MyThread myThread1=new MyThread();
    		Thread thread1=new Thread(myThread1);
    		thread1.start();
    		//创建线程对象thread2且使用setName设置线程名称
    		MyThread myThread2=new MyThread();
    		Thread thread2=new Thread(myThread2);
    		thread2.setName("线程2");
    		thread2.start();
    		//创建线程对象thread3并在创建线程时设置线程名称
    		MyThread myThread3=new MyThread();
    		Thread thread3=new Thread(myThread3,"线程3");
    		thread3.start();
    	}
    }
    

    某次运行结果如下所示:

    当前线程:Thread-0-----i=0
    当前线程:Thread-0-----i=1
    当前线程:Thread-0-----i=2
    当前线程:线程3-----i=0
    当前线程:线程2-----i=0
    当前线程:线程2-----i=1
    当前线程:线程2-----i=2
    当前线程:线程2-----i=3
    当前线程:线程3-----i=1
    当前线程:Thread-0-----i=3
    当前线程:Thread-0-----i=4
    当前线程:线程3-----i=2
    当前线程:线程3-----i=3
    当前线程:线程3-----i=4
    当前线程:线程2-----i=4
    

    通过上述代码及其运行结果可知:
    (1)若没有手动设置线程名称时,会自动分配一个线程的名称,如线程对象thread1自动分配线程名称为Thread-0。
    (2)多线程的运行状态是不确定的,不知道下一个要执行的是哪个线程,这是因为CPU以不确定方式或以随机的时间调用线程中的run()方法。
    (3)需要注意的是,由于设置线程名称是为了区分当前正在执行的线程是哪一个线程,所以在设置线程名称时应避免重复!

    3. 线程休眠------sleep()方法

    线程休眠:指的是让线程暂缓执行,等到预计时间之后再恢复执行。
    (1)线程休眠会交出CPU,让CPU去执行其他的任务。
    (2)调用sleep()方法让线程进入休眠状态后,sleep()方法并不会释放锁,即当前线程持有某个对象锁时,即使调用sleep()方法其他线程也无法访问这个对象。
    (3)调用sleep()方法让线程从运行状态转换为阻塞状态;sleep()方法调用结束后,线程从阻塞状态转换为可执行状态。
    sleep()方法:

    public static native void sleep(long millis) throws InterruptedException;
    
    

    从上面方法参数中可以看出sleep()方法的休眠时间是以毫秒作为单位。
    关于sleep()方法的操作如下:

    class MyThread implements Runnable{
    	@Override
    	public void run() {
    		for(int i=0;i<5;i++)
    		{
    			//使用Thread类的sleep()方法,让线程处于休眠状态
    			try {
    				Thread.sleep(1000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println("当前线程:"+Thread.currentThread().getName()+"-----i="+i);
    		}
    	}
    }
    public class Test1 {
    	public static void main(String[] args){
    		MyThread myThread=new MyThread();
    		//利用myThread对象分别创建三个线程
    		Thread thread1=new Thread(myThread);
    		thread1.start();
    		Thread thread2=new Thread(myThread);
    		thread2.start();
    		Thread thread3=new Thread(myThread);
    		thread3.start();
    	}
    }
    

    某次运行结果如下所示:

    当前线程:Thread-2-----i=0
    当前线程:Thread-1-----i=0
    当前线程:Thread-0-----i=0
    当前线程:Thread-2-----i=1
    当前线程:Thread-0-----i=1
    当前线程:Thread-1-----i=1
    当前线程:Thread-2-----i=2
    当前线程:Thread-1-----i=2
    当前线程:Thread-0-----i=2
    当前线程:Thread-2-----i=3
    当前线程:Thread-1-----i=3
    当前线程:Thread-0-----i=3
    当前线程:Thread-2-----i=4
    当前线程:Thread-0-----i=4
    当前线程:Thread-1-----i=4
    

    注:
    (1)通过运行代码进行观察,发现运行结果会等待一段时间,这就是sleep()方法让原本处于运行状态的线程进入了休眠,从而进程的状态从运行状态转换为阻塞状态。
    (2)以上代码创建的三个线程肉眼观察,发现它们好像是同时进入休眠状态,但其实并不是同时休眠的。

    4. 线程让步------yield()方法

    线程让步:暂停当前正在执行的线程对象,并执行其他线程。
    (1)调用yield()方法让当前线程交出CPU权限,让CPU去执行其他线程。
    (2)yield()方法和sleep()方法类似,不会释放锁,但yield()方法不能控制具体交出CPU的时间。
    (3)yield()方法只能让拥有相同优先级的线程获取CPU执行的机会。
    (4)使用yield()方法不会让线程进入阻塞状态,而是让线程从运行状态转换为就绪状态,只需要等待重新获取CPU执行的机会。
    yield()方法:

    public static native void yield();
    
    

    关于yield()方法的操作如下:

    class MyThread implements Runnable{
    	@Override
    	public void run() {
    		for(int i=0;i<5;i++)
    		{
    			//使用Thread类的yield()方法
    			Thread.yield();
    			System.out.println("当前线程:"+Thread.currentThread().getName()+"-----i="+i);
    		}
    	}
    }
    public class Test1 {
    	public static void main(String[] args){
    		MyThread myThread=new MyThread();
    		//利用myThread对象分别创建三个线程
    		Thread thread1=new Thread(myThread);
    		thread1.start();
    		Thread thread2=new Thread(myThread);
    		thread2.start();
    		Thread thread3=new Thread(myThread);
    		thread3.start();
    	}
    }
    

    某次运行结果如下所示:

    当前线程:Thread-0-----i=0
    当前线程:Thread-0-----i=1
    当前线程:Thread-0-----i=2
    当前线程:Thread-0-----i=3
    当前线程:Thread-0-----i=4
    当前线程:Thread-1-----i=0
    当前线程:Thread-2-----i=0
    当前线程:Thread-2-----i=1
    当前线程:Thread-2-----i=2
    当前线程:Thread-2-----i=3
    当前线程:Thread-2-----i=4
    当前线程:Thread-1-----i=1
    当前线程:Thread-1-----i=2
    当前线程:Thread-1-----i=3
    当前线程:Thread-1-----i=4
    

    5. 等待线程终止------join()方法

    等待线程终止:指的是如果在主线程中调用该方法时就会让主线程休眠,让调用join()方法的线程先执行完毕后再开始执行主线程。
    join()方法:

     public final void join() throws InterruptedException {
            join(0);
        }
    

    注:上面的join()方法是不带参数的,但join()方法还可以带参数
    关于join()方法的操作如下:

    class MyThread implements Runnable{
    	@Override
    	public void run() {
    		for(int i=0;i<2;i++)
    		{
    			//使用Thread类的sleep()方法
    			try {
    				Thread.sleep(3000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println("当前线程:"+Thread.currentThread().getName()+"-----i="+i);
    		}
    	}
    }
    public class Test1 {
    	public static void main(String[] args) throws InterruptedException{
    		MyThread myThread=new MyThread();
    		Thread thread1=new Thread(myThread,"自己创建的线程");
    		thread1.start();
    		System.out.println("主线程:"+Thread.currentThread().getName());
    		//线程对象thread1调用join()方法
    		thread1.join();
    		System.out.println("代码结束");
    	}
    }
    

    运行结果如下所示:

    主线程:main
    当前线程:自己创建的线程-----i=0
    当前线程:自己创建的线程-----i=1
    代码结束
    

    若不调用join()方法的话,运行结果如下所示:

    主线程:main
    代码结束
    当前线程:自己创建的线程-----i=0
    当前线程:自己创建的线程-----i=1
    

    故通过两个运行结果可以更加深刻地感受到调用join()方法后的作用!调用join()方法和不调用join()方法的区别!

    6. 线程停止

    多线程中停止线程有三种方式:
    (1)设置标记位,让线程正常停止。

    class MyThread implements Runnable{
    	//设置标记位
    	private boolean flag=true;
    	public void setFlag(boolean flag) {
    		this.flag = flag;
    	}
    	@Override
    	public void run() {
    		int i=0;
    		while(flag)
    		{
    			System.out.println("第"+(i++)+"次执行-----"+"线程名称:"+Thread.currentThread().getName());
    		}
    	}
    }
    public class Test1 {
    	public static void main(String[] args) throws InterruptedException{
    		MyThread myThread=new MyThread();
    		Thread thread1=new Thread(myThread,"自己创建的线程");
    		thread1.start();
    		//让主线程sleep一毫秒
    		Thread.sleep(1);
    		//修改标记位的值,让自己创建的线程停止
    		myThread.setFlag(false);
    		System.out.println("代码结束");
    	}
    }
    

    运行结果如下所示:

    0次执行-----线程名称:自己创建的线程
    第1次执行-----线程名称:自己创建的线程
    第2次执行-----线程名称:自己创建的线程
    第3次执行-----线程名称:自己创建的线程
    第4次执行-----线程名称:自己创建的线程
    第5次执行-----线程名称:自己创建的线程
    第6次执行-----线程名称:自己创建的线程
    第7次执行-----线程名称:自己创建的线程
    第8次执行-----线程名称:自己创建的线程
    第9次执行-----线程名称:自己创建的线程
    第10次执行-----线程名称:自己创建的线程
    第11次执行-----线程名称:自己创建的线程
    第12次执行-----线程名称:自己创建的线程
    第13次执行-----线程名称:自己创建的线程
    第14次执行-----线程名称:自己创建的线程
    第15次执行-----线程名称:自己创建的线程
    第16次执行-----线程名称:自己创建的线程
    第17次执行-----线程名称:自己创建的线程
    第18次执行-----线程名称:自己创建的线程
    第19次执行-----线程名称:自己创建的线程
    第20次执行-----线程名称:自己创建的线程
    第21次执行-----线程名称:自己创建的线程
    第22次执行-----线程名称:自己创建的线程
    第23次执行-----线程名称:自己创建的线程
    第24次执行-----线程名称:自己创建的线程
    第25次执行-----线程名称:自己创建的线程
    第26次执行-----线程名称:自己创建的线程
    代码结束
    

    (2)使用stop()方法强制使线程退出,但是使用该方法不安全,已经被废弃了!

    class MyThread implements Runnable{
    	@Override
    	public void run() {
    		for(int i=0;i<100;i++)
    		{
    			System.out.println("线程名称:"+Thread.currentThread().getName()+"------i="+i);
    		}
    	}
    }
    public class Test1 {
    	public static void main(String[] args) throws InterruptedException{
    		MyThread myThread=new MyThread();
    		Thread thread1=new Thread(myThread,"自己创建的线程");
    		thread1.start();
    		//让主线程sleep一毫秒
    		Thread.sleep(1);
    		//调用已被弃用的stop()方法去强制让线程退出
    		thread1.stop();
    		System.out.println("代码结束");
    	}
    }
    

    某次运行结果如下所示:

    线程名称:自己创建的线程------i=0
    线程名称:自己创建的线程------i=1
    线程名称:自己创建的线程------i=2
    线程名称:自己创建的线程------i=3
    线程名称:自己创建的线程------i=4
    线程名称:自己创建的线程------i=5
    线程名称:自己创建的线程------i=6
    线程名称:自己创建的线程------i=7
    线程名称:自己创建的线程------i=8
    线程名称:自己创建的线程------i=9
    线程名称:自己创建的线程------i=10
    线程名称:自己创建的线程------i=11
    线程名称:自己创建的线程------i=12
    线程名称:自己创建的线程------i=13
    线程名称:自己创建的线程------i=14
    线程名称:自己创建的线程------i=15
    线程名称:自己创建的线程------i=16
    线程名称:自己创建的线程------i=17
    线程名称:自己创建的线程------i=18
    线程名称:自己创建的线程------i=19
    线程名称:自己创建的线程------i=20
    线程名称:自己创建的线程------i=21
    线程名称:自己创建的线程------i=22
    线程名称:自己创建的线程------i=23
    线程名称:自己创建的线程------i=24
    线程名称:自己创建的线程------i=25
    线程名称:自己创建的线程------i=26
    线程名称:自己创建的线程------i=27
    线程名称:自己创建的线程------i=28
    线程名称:自己创建的线程------i=29
    线程名称:自己创建的线程------i=30
    线程名称:自己创建的线程------i=31
    线程名称:自己创建的线程------i=32
    线程名称:自己创建的线程------i=33
    线程名称:自己创建的线程------i=34
    线程名称:自己创建的线程------i=35
    线程名称:自己创建的线程------i=36
    线程名称:自己创建的线程------i=37
    线程名称:自己创建的线程------i=38
    线程名称:自己创建的线程------i=39
    线程名称:自己创建的线程------i=40
    线程名称:自己创建的线程------i=41
    线程名称:自己创建的线程------i=42
    线程名称:自己创建的线程------i=43
    线程名称:自己创建的线程------i=44
    线程名称:自己创建的线程------i=45
    线程名称:自己创建的线程------i=46
    线程名称:自己创建的线程------i=47
    线程名称:自己创建的线程------i=48
    线程名称:自己创建的线程------i=49
    线程名称:自己创建的线程------i=50
    线程名称:自己创建的线程------i=51线程名称:自己创建的线程------i=51代码结束
    

    从上述代码和运行结果可以看出,原本线程对象thread1的run()方法中应该执行100次语句“System.out.println(“线程名称:”+Thread.currentThread().getName()+"------i="+i);”,但现在没有执行够100次,所以说stop()方法起到了让线程终止的作用。再从运行结果上可以看出,i=51被执行了两次且没有换行,这就体现了调用stop()方法的不安全性!
    下面正式地解释stop()方法为什么不安全?
    因为stop()方法会解除由线程获得的所有锁,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程会立即停止,假如一个线程正在执行同步方法:

    public synchronized void fun(){
    	x=3;
    	y=4;
    }
    

    由于方法是同步的,多线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x=3;时,被调用的stop()方法使得线程即使在同步方法中也要停止,这就造成了数据的不完整性。故,stop()方法不安全,已经被废弃,不建议使用!
    (3)使用Thread类的interrupt()方法中断线程。

    class MyThread implements Runnable{
    	@Override
    	public void run() {
    		int i=0;
    		while(true)
    		{
    			//使用sleep()方法,使得线程由运行状态转换为阻塞状态
    			try {
    				Thread.sleep(1000);
    				//调用isInterrupted()方法,用于判断当前线程是否被中断
    				boolean bool=Thread.currentThread().isInterrupted();
    				if(bool) {
    					System.out.println("非阻塞状态下执行该操作,当前线程被中断!");
    					break;
    				}
    				System.out.println("第"+(i++)+"次执行"+" 线程名称:"+Thread.currentThread().getName());
    			} catch (InterruptedException e) {
    				System.out.println("退出了!");
    				//这里退出了阻塞状态,且中断标志bool被系统自动清除设置为false,所以此处的bool为false
    				boolean bool=Thread.currentThread().isInterrupted();
    				System.out.println(bool);
    				//退出run()方法,中断进程
    				return;
    			}
    		}
    	}
    }
    public class Test1 {
    	public static void main(String[] args) throws InterruptedException{
    		MyThread myThread=new MyThread();
    		Thread thread1=new Thread(myThread,"自己创建的线程");
    		thread1.start();
    		//让主线程sleep三秒
    		Thread.sleep(3000);
    		//调用interrupt()方法
    		thread1.interrupt();
    		 System.out.println("代码结束");
    	}
    }
    

    运行结果如下所示 :

    0次执行 线程名称:自己创建的线程
    第1次执行 线程名称:自己创建的线程
    第2次执行 线程名称:自己创建的线程
    代码结束
    退出了!
    false
    

    (1)interrupt()方法只是改变中断状态而已,它不会中断一个正在运行的线程。具体来说就是,调用interrupt()方法只会给线程设置一个为true的中断标志,而设置之后,则根据线程当前状态进行不同的后续操作。
    (2)如果线程的当前状态出于非阻塞状态,那么仅仅将线程的中断标志设置为true而已;
    (3)如果线程的当前状态出于阻塞状态,那么将在中断标志设置为true后,还会出现wait()、sleep()、join()方法之一引起的阻塞,那么会将线程的中断标志位重新设置为false,并抛出一个InterruptedException异常。
    (4)如果在中断时,线程正处于非阻塞状态,则将中断标志修改为true,而在此基础上,一旦进入阻塞状态,则按照阻塞状态的情况来进行处理。例如,一个线程在运行状态时,其中断标志设置为true之后,一旦线程调用了wait()、sleep()、join()方法中的一种,立马抛出一个InterruptedException异常,且中断标志被程序自动清除,重新设置为false。
    总结:调用Thread类的interrupted()方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。因此,通过interrupted()方法真正实现线程的中断原理是 :开发人员根据中断标志的具体值来决定如何退出线程。

    7.线程等待------wait()方法

    首先,wait()方法是Object类的方法,下面是无参的wait()方法:

    public final void wait() throws InterruptedException {
            wait(0);
        }
    

    (1)wait()方法的作用是让当前正在执行的线程进入线程阻塞状态的等待状态,该方法时用来将当前线程置入“预执行队列”中,并且调用wait()方法后,该线程在wait()方法所在的代码处停止执行,直到接到一些通知或被中断为止。
    (2)wait()方法只能在同步代码块或同步方法中调用,故如果调用wait()方法时没有持有适当的锁时,就会抛出异常。
    (3)wait()方法执行后,当前线程释放锁并且与其他线程相互竞争重新获得锁。
    下面调用wait()方法:

    public class Test1 {
    	public static void main(String[] args) {
    		Object object=new Object();
    		synchronized (object) {
    			System.out.println("调用wait()前");
    			//调用Object类的wait()方法
    			try {
    				object.wait();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println("调用wait()后");
    		}
    	} 
    }
    

    运行结果如下所示:

    调用wait()

    解析:此时,程序依然处于执行状态。原本应该打印两条语句:调用wait()前 调用wait()后,但是由于该程序还没有运行完而且只打印了一条语句:调用wait()前。这是因为什么呢?因为调用了Object类的wait()方法,使得程序在执行wait()方法之后一直等待下去,故只执行了调用wait()方法前的语句。但程序不能这样一直等待下去,这个时候就需要另一个方法去唤醒调用wait()方法的处于等待状态的线程,让等待线程继续执行下去,该方法为notify()方法。

    8. 线程唤醒-------notify()方法

    首先,notify()方法也是Object类的方法:

    public final native void notify();
    

    (1)notify()方法要在同步代码块或同步方法中调用。
    (2)notify()方法是用来通知那些等待该对象的对象锁的线程,对其调用wait()方法的对象发出通知让这些线程不再等待,继续执行。
    (3)如果有多个线程都在等待,则由线程规划器随机挑选出一个呈wait状态的线程将其线程唤醒,继续执行该线程。
    (4)调用notify()方法后,当前线程并不会马上释放该对象锁,要等到执行notify()方法的线程执行完才会释放对象锁。
    下面调用notify()方法:

    class MyThread implements Runnable{
    	private boolean flag;
    	private Object object;
    	//定义一个构造方法
    	public MyThread(boolean flag,Object object) {
    		this.flag=flag;
    		this.object=object;
    	}
    	//定义一个普通方法,其中调用了wait()方法
    	public void waitThread() {
    		synchronized (this.object) {
    			try {
    				System.out.println("调用wait()前------"+Thread.currentThread().getName());
    				//调用wait()方法
    				this.object.wait();
    				System.out.println("调用wait()后------"+Thread.currentThread().getName());
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    	//定义一个普通方法,其中调用了notify()方法
    	public void notifyThread() {
    		synchronized (this.object) {
    			try {
    				System.out.println("调用notify前------"+Thread.currentThread().getName());
    				//调用notify()方法
    				this.object.notify();
    				System.out.println("调用notify()后------"+Thread.currentThread().getName());
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	}
    	@Override
    	public void run() {
    		if(this.flag) {
    			this.waitThread();
    		}else {
    			this.notifyThread();
    		}
    	}	
    }
    public class Test1 {
    	public static void main(String[] args) {
    		Object object=new Object();
    		//实例化调用wait()的线程
    		MyThread wait=new MyThread(true,object);
    		Thread waitThread=new Thread(wait,"wait线程");
    		//实例化调用notify()的线程
    		MyThread notify=new MyThread(false,object);
    		Thread notifyThread=new Thread(notify,"notify线程");
    		//启动线程
    		waitThread.start();
    		//调用一下sleep()方法,使得查看效果更明显
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		notifyThread.start();
    	} 
    }
    

    运行结果如下所示:

    调用wait()------wait线程
    调用notify前------notify线程
    调用notify()------notify线程
    调用wait()------wait线程
    

    解析:根据run方法及实例化的线程对象,wait线程执行了waitThread()方法,该方法中调用了wait()方法使得线程进入线程阻塞状态的等待状态并释放锁,如果没有其他线程去唤醒该线程的话wait线程将一直等待下去。此时,notify线程执行了notifyThread()方法,该方法中调用了notify()方法,使得notify线程去唤醒wait线程,让wait线程不再等待下去,并且先将notify线程执行完后释放锁再执行wait线程的wait()方法之后的语句。所以打印如上所示。
    但要注意的是,当有多个线程处于等待时,调用notify()方法唤醒线程时,就会依然有线程处于等待状态,演示如下:

    class MyThread implements Runnable{
    	private boolean flag;
    	private Object object;
    	//定义一个构造方法
    	public MyThread(boolean flag,Object object) {
    		this.flag=flag;
    		this.object=object;
    	}
    	//定义一个普通方法,其中调用了wait()方法
    	public void waitThread() {
    		synchronized (this.object) {
    			try {
    				System.out.println("调用wait()前------"+Thread.currentThread().getName());
    				//调用wait()方法
    				this.object.wait();
    				System.out.println("调用wait()后------"+Thread.currentThread().getName());
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    	//定义一个普通方法,其中调用了notify()方法
    	public void notifyThread() {
    		synchronized (this.object) {
    			try {
    				System.out.println("调用notify前------"+Thread.currentThread().getName());
    				//调用notify()方法
    				this.object.notify();
    				System.out.println("调用notify()后------"+Thread.currentThread().getName());
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	}
    	@Override
    	public void run() {
    		if(this.flag) {
    			this.waitThread();
    		}else {
    			this.notifyThread();
    		}
    	}	
    }
    public class Test1 {
    	public static void main(String[] args) {
    		Object object=new Object();
    		//实例化调用wait()的线程
    		MyThread wait=new MyThread(true,object);
    		Thread waitThread1=new Thread(wait,"wait线程1");
    		Thread waitThread2=new Thread(wait,"wait线程2");
    		Thread waitThread3=new Thread(wait,"wait线程3");
    		//实例化调用notify()的线程
    		MyThread notify=new MyThread(false,object);
    		Thread notifyThread=new Thread(notify,"notify线程");
    		//启动3个等待线程
    		waitThread1.start();
    		waitThread2.start();
    		waitThread3.start();
    		//调用一下sleep()方法,使得查看效果更明显
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		notifyThread.start();
    	} 
    }
    

    运行结果如下所示:

    调用wait()------wait线程1
    调用wait()------wait线程3
    调用wait()------wait线程2
    调用notify前------notify线程
    调用notify()------notify线程
    调用wait()------wait线程1
    

    此时程序并没有执行完毕,因为依然有线程处于等待状态,所以notify()只是随机将某一个等待线程唤醒,并没有唤醒所有等待线程。那么,若有多个线程处于等待状态时,如何将所有等待线程都唤醒呢?下面将介绍notifyAll()方法

    9. notifyAll()方法

    Object类的notifyAll()方法:

    public final native void notifyAll();
    
    

    notifyAll()方法将同一对象锁的所有等待线程全部唤醒。代码演示如下:

    class MyThread implements Runnable{
    	private boolean flag;
    	private Object object;
    	//定义一个构造方法
    	public MyThread(boolean flag,Object object) {
    		this.flag=flag;
    		this.object=object;
    	}
    	//定义一个普通方法,其中调用了wait()方法
    	public void waitThread() {
    		synchronized (this.object) {
    			try {
    				System.out.println("调用wait()前------"+Thread.currentThread().getName());
    				//调用wait()方法
    				this.object.wait();
    				System.out.println("调用wait()后------"+Thread.currentThread().getName());
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    	//定义一个普通方法,其中调用了notifyAll()方法
    	public void notifyThread() {
    		synchronized (this.object) {
    			try {
    				System.out.println("调用notify前------"+Thread.currentThread().getName());
    				//调用notifyAll()方法
    				this.object.notifyAll();
    				System.out.println("调用notify()后------"+Thread.currentThread().getName());
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	}
    	@Override
    	public void run() {
    		if(this.flag) {
    			this.waitThread();
    		}else {
    			this.notifyThread();
    		}
    	}	
    }
    public class Test1 {
    	public static void main(String[] args) {
    		Object object=new Object();
    		//实例化调用wait()的线程
    		MyThread wait=new MyThread(true,object);
    		Thread waitThread1=new Thread(wait,"wait线程1");
    		Thread waitThread2=new Thread(wait,"wait线程2");
    		Thread waitThread3=new Thread(wait,"wait线程3");
    		//实例化调用notifyAll()的线程
    		MyThread notify=new MyThread(false,object);
    		Thread notifyThread=new Thread(notify,"notify线程");
    		//启动3个等待线程
    		waitThread1.start();
    		waitThread2.start();
    		waitThread3.start();
    		//调用一下sleep()方法,使得查看效果更明显
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		notifyThread.start();
    	} 
    }
    

    运行结果如下所示:

    调用wait()------wait线程1
    调用wait()------wait线程2
    调用wait()------wait线程3
    调用notify前------notify线程
    调用notify()------notify线程
    调用wait()------wait线程3
    调用wait()------wait线程2
    调用wait()------wait线程1
    

    解析:此时,程序执行完毕,所有等待线程都被调用notifyAll()方法的具有同一对象锁的线程唤醒,故每一个等待线程都会在调用wait()后继续执行直到该线程结束。

    说说sleep()方法和wait()方法的区别和共同点?

    1.sleep()方法属于Thread类,而wait()方法属于Object类
    2.sleep()方法不需要在synchronized中调用,wait()方法只能在同步代码块或同步方法中调用
    3.在调用sleep()方法的过程中,线程不会释放对象锁,而wait()方法释放了锁。
    4.这两个方法都可以暂停线程的执行。
    5.sleep()方法执行完以后,线程会自动苏醒。或者使用wait(long timeout)超时后线程会自动苏醒。wait()被调用后不会自动苏醒,需要别的线程调用同一个对象上的notify()或者notifyAll()方法。
    6.sleep方法是静态方法,而wait方法不是静态方法。
    7.sleep方法在当前线程上执行,而wait方法在调用它的对象上执行。

    线程安全

    什么时候会发生线程安全问题?实际上就是解决线程同步问题
    答:当多个线程共享一个成员变量或者静态成员变量,做写的时候,可能会受到其他线程的干扰,导致数据有问题,会发生线程安全,这种现象叫线程安全问题。
    也可以这么说:当多个线程访问同一个数据的时候,可能引起冲突。而且这些线程中至少有一个线程会改写这个数据时才会出现冲突,如果所有线程都只读不改写,则不会冲突。由于这个数据不只被一个线程访问,我们称这个数据为共享数据。

    类中一般会出现三种数据:

    • 静态成员变量
    • 普通成员变量
    • 方法里定义的局部变量(包括方法参数变量)。

    对于局部变量,不管是静态方法还是动态方法,都不会存在线程安全问题。原因是:
    方法在执行的时候,方法里的局部变量都是存储在堆栈上的。而方法的堆栈分配,是在方法每次执行开始时系统分配的,所以即使是调用同一方法(不管是不是同一线程),所分配的堆栈也是不同的。
    对于普通成员变量,同一个类的不同的对象实例有不同的成员变量,所以多个线程如果可以确定是在操作不同的对象,就不会存在冲突。如果多个线程操作的是同一个对象实例,则需要考虑对类内普通成员变量操作的线程安全问题。
    对于静态成员变量,因为这种变量在内存中只有一个副本,所以无论什么时候操作这种变量都需要考虑线程安全问题。
    我们通过一个案例,演示线程的安全问题:
    说明:电影院卖票,使用了A、B、C三个窗口进行卖票,电影票总数为100张
    采用线程对象来模拟卖票窗口A、B、C;使用Runnable接口的子类来模拟买的电影票
    模拟电影票:

    public class Ticket implements Runnable{
        // 在成员位置 定义票的总数100
        int ticket = 100;
     
        @Override
        public void run() {
            // 模拟买票窗口
            // 买票窗口永远开启
            while (true){
                // 判断是否还有票可以卖
                if(ticket > 0){
                    // 使用sleep增加“程序的时间”--每张票卖50ms
                    try {
                        Thread.sleep(50);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    // 获得线程名称 即买票窗口名称
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "卖掉第" + ticket-- + "票");
                }
            }
        }
    }
    

    模拟买票:

    /**
     * 模拟买票操作
     *    假设一场电影有100张票
     *    三个窗口同时买票
     *
     *    窗口  线程对象
     *    买票  线程任务 实现runnable接口
     */
    public class Demo {
        public static void main(String[] args) {
            // 创建买票任务对象
            Ticket ticket = new Ticket();
     
            // 创建三个窗口
            Thread t1 = new Thread(ticket, "窗口A");
            Thread t2 = new Thread(ticket, "窗口B");
            Thread t3 = new Thread(ticket, "窗口C");
     
            // 开启线程
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

    运行结果:

    窗口A卖掉第100张票
    窗口C卖掉第98张票
    窗口B卖掉第99张票
    窗口A卖掉第97张票
    窗口B卖掉第95张票
    窗口C卖掉第96张票
    窗口C卖掉第94张票 ⇐
    窗口B卖掉第94张票 ⇐
    窗口A卖掉第94张票 ⇐
    ...
    窗口C卖掉第1张票
    窗口A卖掉第0张票
    窗口B卖掉第-1张票 ⇐
    

    发现程序出现了两个问题:

    1. 相同的票数被卖了多次,如第94张被三个窗口都卖了
    2. 卖出了不存在的票,如窗口B卖掉了第-1张票
    

    此时,几个窗口(线程)票数不同步了,这种问题称为线程不安全。

    线程安全问题都是由全局变量即静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作。一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全

    线程同步

    当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题
    要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复卖同一张票和卖不存在的票问题,Java中提供了同步机制(synchronized)来解决
    根据案例简述:

    窗口A线程进入操作(买票)的时候,窗口B和窗口C线程只能在外等着,
    窗口A操作结束,窗口A、窗口B和窗口C(CPU分配内存是随机的,所以还有可能是窗口A进入)才有机会进入代码去执行。
    也就是说,在某个线程修改共享资源的时候,其他线程不能修改该资源,
    等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
    

    为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。

    实现线程同步的几种方式:

    1. 同步方法
    2. 同步代码块
    3. 使用特殊域变量(volatile)实现线程同步 
    4. 使用重入锁实现线程同步
    5. 使用局部变量实现线程同步 
    

    同步代码块
    同步代码块:synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
    格式:

    synchronized(同步锁){
        // 需要同步的操作的代码
    }
    

    同步锁:
    对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。

    1. 锁对象可以是任意类型
    2. 多个线程对象要使用同一把锁
    

    注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到就进入代码块,其他的线程只能在外等着
    使用同步代码块解决卖票问题:

    /**
     * synchronized(锁对象){
     *
     * }
     * 1. 锁对象可以是任意类型
     * 2. 互斥线程需要使用同一把锁
     */
    public class Ticket implements Runnable{
        // 在成员位置 定义票的总数100
        int ticket = 100;
     
        Object obj = new Object();
     
        @Override
        public void run() {
            // 模拟买票窗口
            // 买票窗口永远开启
            while (true){
                // 同步锁
                synchronized (obj){
                    // 判断是否还有票可以卖
                    if(ticket > 0){
                        // 使用sleep增加“程序的时间”--每张票卖50ms
                        try {
                            Thread.sleep(50);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        // 获得线程名称 即买票窗口名称
                        String name = Thread.currentThread().getName();
                        System.out.println(name + "卖掉第" + ticket-- + "票");
                    }
                }
     
            }
        }
    }
    

    执行结果:

    窗口A卖掉第100票
    窗口C卖掉第99票
    窗口B卖掉第98票
    窗口B卖掉第97...
    窗口C卖掉第4票
    窗口A卖掉第3票
    窗口A卖掉第2票
    窗口A卖掉第1

    此时,每张票都只会被卖掉一次,不会存在卖掉不存在的电影票的问题。
    当使用了同步代码块后,上述的线程的安全问题即可解决

    线程池

    看线程池请点击

    乐观锁和悲观锁

    一、何谓悲观锁与乐观锁

    乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。

    二、悲观锁

    总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

    三、乐观锁

    总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

    四、两种锁的使用场景
    从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

    五、乐观锁常见的两种实现方式
    乐观锁一般会使用

    • 版本号机制
    • CAS算法实现。

    1. 版本号机制

    一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
    举一个简单的例子: 假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。

    • 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
    • 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20)。
    • 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣除后余额( balance=$50),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
    • 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数据( balance=$80),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足 “提交版本必须大于记录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。

    这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。

    2. CAS算法
    即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数

    • 需要读写的内存值 V
    • 进行比较的值 A
    • 拟写入的新值 B

    当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
    自旋锁参考:https://blog.csdn.net/qq_34337272/article/details/81252853

    乐观锁的缺点

    ABA 问题是乐观锁一个常见的问题

    1 ABA 问题
    如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。

    JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

    2 循环时间长开销大
    自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory
    order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

    3 只能保证一个共享变量的原子操作
    CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。

    CAS与synchronized的使用情景

    简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)

    • 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
    • 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

    补充: Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 “重量级锁” 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁 和 轻量级锁 以及其它各种优化之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

    双重校验锁/volatile关键字

    public class Singleton {  
        private volatile static Singleton singleton;  
        private Singleton (){}  
        public static Singleton getSingleton() {  
        if (singleton == null) {  
            synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
            }  
        }  
        return singleton;  
        }  
    }
    

    一、并发编程的三个重要特性

    • 原子性 :一个的操作或者多次操作,要么所有的操作全部都得到执行并且不会收到任何因素的干扰而中断,要么所有的操作都执行,要么都不执行。synchronized可以保证代码片段的原子性。
    • 可见性 :当一个变量对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。volatile 关键字可以保证共享变量的可见性。
    • 有序性 :代码在执行的过程中的先后顺序,Java 在编译器以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序。volatile 关键字可以禁止指令进行重排序优化。

    根据java内存模型:
    volatile 关键字的主要作用

    • 保证变量的可见性
    • 还有一个作用是防止指令重排序。

    二、说说 synchronized 关键字和 volatile 关键字的区别

    synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在:

    • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用synchronized 关键字的场景还是更多一些
    • 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞
    • volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。
    • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。

    https://mp.weixin.qq.com/s/CNbQgSZ71bZAmVpvz0kP-A

    展开全文
  • 1、线程组 将多个线程组合到一起,可以对一批线程直接管理,java允许对线程组直接控制。 2.线程组ThreadGroup类 ... public final String getName():获取线程组对象的名称 Thread.currentThread().g...
    1、线程组
    	将多个线程组合到一起,可以对一批线程直接管理,java允许对线程组直接控制。
    	
    2.线程组ThreadGroup类
    	public final ThreadGroup getThreadGroup():获取线程组对象,默认所有的线程都属于主线程组
    	public final String getName():获取线程组对象的名称
    	Thread.currentThread().getThreadGroup().getName():获取当前线程组的名称
    	
    	ThreadGroup(String name):设置线程组名称
    	Thread(ThreadGroup group, Runnable target, String name):设置线程属于哪个组以及线程实现的接口、线程组的名称
    	
    3、线程池
    	线程池里的每一个线程结束后并不会死亡,而是回到线程组变为就绪状态,等待下一个对象来使用。
    	
    4、创建线程池对象的方法
    	Executors工厂类的方法:
    		public static ExecutorService newFixedThreadPool(int nThreads):线程池中创建指定数量的线程对象
    		public static ExecutorService newCachedThreadPool():
    		public static ExecutorService newSimpleThreadPool():线程池中含有单个线程
    		
    	线程池执行实现run()方法的Runnable接口的对象或者实现call()方法的Callable接口的对象的线程
    		Future<T> submit(Runnable task):
    		<T> Future<T> submit(Callable<T> task):
    		
    	关闭线程池:
    		pool.shutdown()
    		
    5、创建线程的第三种方法:
    	实现Callable接口,但这种方式只能依赖线程池
    	Callable接口是带有泛型的接口,其泛型就是实现的call()方法的返回值类型
    	
    	实例:
    		import java.util.concurrent.Callable;
    
    		public class MyCallable implements Callable<Object> {
    			private static int num = 0;
    
    			MyCallable(int num) {
    				this.num = num;
    			}
    
    			@Override
    			public Object call() throws Exception {
    				for (int i = 0; i < num; i++) {
    					System.out.println(Thread.currentThread().getThreadGroup().getName()+
    							Thread.currentThread().getName()+":"+i);
    				}
    				return null;
    			}
    
    		}
    
    		import java.util.concurrent.ExecutionException;
    		import java.util.concurrent.ExecutorService;
    		import java.util.concurrent.Executors;
    		import java.util.concurrent.Future;
    
    		public class demo3 {
    			public static void main(String[] args) throws InterruptedException, ExecutionException {
    				//创建线程池
    				ExecutorService pool = Executors.newFixedThreadPool(2);
    				//执行实现call()方法的Callable接口的线程对象
    				Future<Object> f1 = pool.submit(new MyCallable(50));
    				Future<Object> f2 = pool.submit(new MyCallable(100));
    				//获取call()方法的返回值对象
    				Object obj1 = f1.get();
    				Object obj2 = f2.get();
    				
    				System.out.println(obj1);
    				System.out.println(obj2);
    				//关闭线程池
    				pool.shutdown();
    			}
    		}
    

     

    展开全文
  • 1.多线程 1.1多线程示例代码: //我的线程类 继承线程类 public class MyTread extends Thread { //重写Thread类中的run...//获取线程名称,例如Thread-0、Thread-1 //或 Thread.currentThread().getName(); for(in
  • 1.名称的意思是,线程池管理的是一个工作者线程的同构池。其中线程池与工作队列密切相关, (工作队列:持有所有等待执行的任务) 2.工作者线程的工作变得轻松:每次从工作队列中获取下一个任务,执行它,然后回来...
  • 线程、同步、线程池

    2020-08-06 12:57:04
    一、线程 创建线程的方式一: 1.继承Thread类 包名:java.lang.Thread 构造方法: ...getName:获取线程名称 start:启动线程,调用线程中的run 方法 run:线程要执行任务的代码 sleep:线程要多久才执行,暂
  • 实现Runnable三、如何获取线程名称四、休眠五、线程中断六、守护线程七、线程安全1. 同步代码块2.同步方法3. 显示锁 Lock \ 子类 ReentrantLock八、线程死锁九、多线程通信十、线程六种状态:十一、线程池1. 缓存...
  • 复习: 线程:两种实现方式:extends Thread 还有implements和Runnable这两个不是线程类 ...currentThread()获取正在执行的线程对象 getName()返回线程名称 sleep()方法,表示的线程休眠 join()等待线程执行结束 生
  • Java线程Thread

    2017-10-12 09:16:22
    java中多线程运行原理掌握两种线程创建方式两种创建线程方式好处和弊端掌握使用Thread类 中获取和设置线程名称的方法使用匿名内部类创建多线程描述java线程池的运行原理线程安全问题出现的原因使用同步代码块\同步...
  • 一个目录里边有 47W个文件,现在需要根据一个文本文件里边的文件名称 在47W个文件中取出24W个有效文件(根据文本文件里边的文件名取) 二、设计思路 1、使用 BufferedReader逐行读取文本文件,并存入LinkList ...
  • Java学习-多线程

    2020-07-24 22:25:21
    多线程多线程简介实现线程的两种方式继承Thread类实现Runnable接口操作线程的方法线程的休眠线程的唤醒线程的中断设置和获取线程名称线程安全问题线程安全1-同步代码块-隐式锁线程安全2-同步方法-隐式锁线程安全3-...
  • Java学习——多线程一 一些概念二 继承Thread三 实现Runnable四,Thread类1 设置与获取线程名称2 线程休眠sleep3 线程中断五 线程不安全1,现象与原因2,解决方案(1)同步代码块(2)同步方法(3)显式锁六 线程...
  • java学习之多线程

    2020-09-11 21:16:39
    设置获取线程名称和线程休眠方法 守护线程: 线程安全问题: 显式锁和隐式锁的区别: 线程池: 同步&&异步 同步:排队执行效率低但是安全; 异步:同时执行效率高但是数据不安全; 并发&&...
  • java线程的学习笔记

    2020-08-06 20:54:17
    //、获取当前线程名称 Thread.currentThread().getName() 1.start():启动当前线程、调用线程中的run方法 2.run()通常需要重写Thread类中的方法,将创建的线程要执行的操作声明在此方法中 3.currentThread()静态...
  • 线程的创建2.1 创建线程(一)2.2 获取和修改线程名称2.3 一个线程小案例2.4 创建线程(二)2.5 Runnable小案例3. 线程的基本状态4. 线程常用方法5. 线程安全5.1 同步方式(1)5.2 线程基本状态(源码补充)5.3 ...
  • Java线程(小结)

    2020-08-18 22:47:52
    多线程知识点多线程1、线程与进程2、线程调度3、同步与异步4、并发与并行5、多线程创建的方式(1)继承Thread类(2)实现Runnable接口(3)实现Callable接口(4)通过线程池创建6、获取和设置线程名称7、线程中断8、...
  • 线程

    2020-11-04 21:12:26
    4.1获取线程名称 4.2设置线程名称 4,3获取当前线程对象 4.4线程休眠 4.5守护线程 4.6,线程优先级 5,线程安全问题 6,同步代码块解决线程安全问题 7,同步方法解决线程安全问题 8,线程状态 9,线程池 1...
  • 文章目录线程和进程:Thread类:线程启动:设置和获取线程名称:线程的生命周期线程的方法线程池实现异步求和(线程池案例)线程复习思维导图: 线程和进程: 线程:程序执行流的最小单元 进程:进程是线程的容器 ...
  • 线程创建创建线程或线程池时指定有意义的线程名称,方便出错时回溯。线程池线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源...
  • Java&day10

    2021-01-09 16:30:18
    线程获取到了不同的锁 (同步代码块的嵌套) 说出线程6个状态的名称 新建状态 可运行状态 锁阻塞状态 无限等待状态 计时等待状态 终止状态 理解等待唤醒案例 wait() :等待,调用这个方法之后线程会释放...
  • Java代码规范

    2020-03-05 21:14:01
    并发处理 【强制】获取单例对象需要保证线程...【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。 正例: public class TimerTaskThread extends Thread { public TimerTaskThread() { su...
  •  2、创建线程或线程池时 请指定有意义的线程名称,方便出错时回溯  3、线程资源必须通过线程池提供,不允许在应用中自行显示创建线程  4、线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式...
  • 1. 【强制】获取单例...2. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。 正例:public class TimerTaskThread extends Thread {public TimerTaskThread() {super.setName("TimerTaskThre...
  • 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。 正例:自定义线程工厂,并且根据外部特征进行分组,比如,来自同一机房的调用,把机房编号赋值给 whatFeaturOfGroup public class ...
  • 【强制】获取单例对象需要保证...【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。 正例: public class TimerTaskThread extends Thread { public TimerTaskThread() { super.setNam...
  • 2. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。 3. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所花的...
  • 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。   正例:   public class TimerTaskThread extends Thread {   public TimerTaskThread() {   super.setName...
  • Java开发规范(二)

    2017-09-01 23:27:41
    6、并发处理 1. 获取单例对象需要保证线程...2. 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。  正例: public class TimerTaskThread extends Thread { public TimerTaskThread() { super.se
  • 转载自 阿里巴巴对Java编程【并发处理】的规约并发处理...2. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。正例:public class TimerTaskThread extends Thread { public TimerTaskThread(...

空空如也

空空如也

1 2 3
收藏数 54
精华内容 21
关键字:

java线程池获取线程名称

java 订阅