多线程----Thread类,Runnable接口,线程池,Callable接口,线程安全
1概念
1.1进程
进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
任务管理器中:
1.2线程
线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
1.3多线程
就是一个程序中有多个线程在同时执行。
单线程程序:多个任务只能依次执行。只有一个线程, 例:main方法中调用多个方法,效率较低
多线程程序:多个任务可以同时执行。
1.4程序运行原理
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。
实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
1.5主线程
程序从上往下执行的过程:
javac编译----JVM运行Demo01----找到main方法,运行----找到操作系统OS,开启线程
对于CPU就有了一条执行路径,运行方法main的这条路径就叫main,即主线程。
例:
public class Demo01 {
//主线程
public static void main(String[] args) {
method01();
System.out.println("-9的绝对值是:"+Math.abs(-9));
}
public static void method01(){
for(int i=0;i<10;i++){
System.out.println(i);
}
}
}
加一条错误语句:
可以看到异常发生在main线程中
2 Thread类
Thread是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
常用方法:
创建新执行线程有两种方法:
2.1方法1
定义Thread子类,继承Thread,重写run方法
在测试类中创建Thread子类对象
子类对象调用start方法,并且只能调用一次
例:
public class ThreadDemo extends Thread{
public void run() {
for(int i=0;i<10;i++){
System.out.println("Thread:"+i);
}
}
}
public class Test01 {
public static void main(String[] args) {
ThreadDemo td=new ThreadDemo();
td.start();
for(int i=0;i<10;i++){
System.out.println("main:"+i);
}
}
}
start()方法的两个任务:
1)让线程执行
2)让JVM调用线程中的run方法
内存图:
图说明:
Main进栈
看到New Thread,start(),会开线程,会再开一个新的栈,run方法进入新栈
两个栈就是两个线程
每new一个Thread,就多一个栈
注意:要执行start方法,不是run方法,
这就不是多线程了,是正常调用方法。
2.2获取线程名称
例:
public class ThreadDemo extends Thread{
public void run() {
System.out.println("线程名为:"+getName());
}
}
public class Test02 {
public static void main(String[] args) {
ThreadDemo td=new ThreadDemo();
td.run();
Thread td2=Thread.currentThread();
System.out.println("主线程名为:"+td2.getName());
}
}
注意:主线程的名字只能用Thread.currentThread()这种方式获取,如果这样:
这里因为main方法是static,而getName()不是static,静态不能调用非静态,所以即使Test03继承了Thread也不能直接用getName()。
2.3修改线程名称(了解即可,尽量不要修改,没有必要)
1)setName()方法
public class ThreadDemo extends Thread{
public void run() {
System.out.println(getName());
}
}
public class Test03 extends Thread{
public static void main(String[] args) {
ThreadDemo td=new ThreadDemo();
td.setName("改名1");
td.run();
}
}
2)构造方法
public class ThreadDemo extends Thread{
public ThreadDemo(){ }
public ThreadDemo(String name){
super(name);
} public void run() {
System.out.println(getName());
}
}
public class Test03 extends Thread{
public static void main(String[] args) {
ThreadDemo td=new ThreadDemo("改名2");
td.run();
}
}
Tips:主线程无法改名,就叫main
2.4 sleep()方法
public class ThreadDemo extends Thread{
public void run() {
for(int i=0;i<5;i++){
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(i);
}
}
}
public class Test03 extends Thread{
public static void main(String[] args) {
ThreadDemo td=new ThreadDemo();
td.run();
}
}
3创建线程方式2—实现Runnable接口
3.1步骤
- 创建实现类,实现Runnable接口,重写run方法
- 在测试类中创建实现类对象,创建线程对象
- 将实现类对象传入线程对象的构造方法
- 用线程对象开启线程
例:
public class MyRunnable implements Runnable{
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class Demo01 {
public static void main(String[] args) {
//创建线程任务对象
MyRunnable mr=new MyRunnable();
//创建线程对象1
Thread t1=new Thread(mr);
//开启线程1
t1.start(); //创建线程对象2
Thread t2=new Thread(mr);
//开启线程2
t2.start();
}
}
3.2好处
实现Runnable接口,避免了继承Thread类的单继承局限性,较为常用
实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。
继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。
总结:把线程任务的定义,和线程的创建,分开了
高内聚,低耦合
3.3线程的匿名内部类使用
public class Demo02 {
public static void main(String[] args) {
Runnable r=new Runnable(){
public void run() {
System.out.println("重写后的run方法");
}
};
//创建线程
Thread t=new Thread(r);
//开启线程
t.start();
}
}
简写:
public class Demo2 {
public static void main(String[] args) {
new Thread(new Runnable(){
public void run() {
System.out.println("重写后的run方法");
}
}).start();
}
}
4线程的状态
在Thread类中有个内部类:
线程状态。线程可以处于下列状态之一:
NEW
至今尚未启动的线程处于这种状态。
RUNNABLE
正在 Java 虚拟机中执行的线程处于这种状态。
BLOCKED
受阻塞并等待某个监视器锁的线程处于这种状态。
WAITING
无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
TIMED_WAITING
等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
TERMINATED
已退出的线程处于这种状态。
图示:
5线程池
5.1概念
线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
5.2使用线程池方式--Runnable接口
通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。
1)Executors:线程池创建工厂类
public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象
2)ExecutorService:线程池类
Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行
3)Future接口:用来记录线程任务执行完毕后产生的结果。
使用线程池中线程对象的步骤:
创建线程池对象
创建Runnable接口子类对象
提交Runnable接口子类对象
关闭线程池
例:
public class MyRunnable implements Runnable{
public void run() {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class Demo03 {
public static void main(String[] args) {
//从线程池工厂获取线程池对象
ExecutorService es=Executors.newFixedThreadPool(2); //2条线程的线程池
//线程池中抽取一个有空的线程执行线程任务
es.submit(new MyRunnable());
es.submit(new MyRunnable());
//销毁线程池
es.shutdown();
}
}
5.3使用线程池方式—Callable接口
1)Callable接口:
与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。
2)ExecutorService:线程池类
<T> Future<T> submit(Callable<T> task):获取线程池中的某一个线程对象,并执行线程中的call()方法
3)Future接口:用来记录线程任务执行完毕后产生的结果。
使用线程池中线程对象的步骤:
创建线程池对象
创建Callable接口子类对象
提交Callable接口子类对象
关闭线程池
例:
public class MyCallable implements Callable<String>{
public String call() throws Exception {
return "abc";
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; public class Demo01 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//从线程池工厂获取线程池对象
ExecutorService es=Executors.newFixedThreadPool(2);
//获取call方法执行后的Future对象
Future<String> str=es.submit(new MyCallable());
//从Future对象中获取返回值
String s=str.get();
System.out.println(s);
//销毁线程池
es.shutdown();
}
}
5.4练习:从1到100的和,从1到200的和
import java.util.concurrent.Callable; public class CallSum implements Callable<Integer>{
//定义成员变量
private int num; //构造方法
public CallSum(){
};
public CallSum(int num){
this.num=num;
}; //线程任务
public Integer call() throws Exception {
int sum=0;
for(int i=0;i<=num;i++){
sum+=i;
}
return sum;
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es=Executors.newFixedThreadPool(2); Future<Integer> f=es.submit(new CallSum(100));
System.out.println(f.get()); Future<Integer> f2=es.submit(new CallSum(200));
System.out.println(f2.get()); es.shutdown();
}
}
6 线程安全
6.1线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
例:电影院卖票
代码示例:
public class MyTicker implements Runnable{
private int ticket=100; //100张票
//线程任务
public void run() {
while(true){
if(ticket>0){
try {
Thread.sleep(10); //模拟资源被抢占
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票");
}
}
}
}
public class SaleTicket {
public static void main(String[] args) {
//创建线程任务
MyTicker mt=new MyTicker();
//创建线程
Thread t0=new Thread(mt);
Thread t1=new Thread(mt);
Thread t2=new Thread(mt);
//开启线程
t0.start();
t1.start();
t2.start();
}
}
在多线程访问同一个资源的时候,往往会出现安全问题。虽然不是一定发生,但是只要可能发生,这就是多线程的安全隐患,是不允许的。
其实,线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
6.2线程同步
java中提供了线程同步机制,它能够解决上述的线程安全问题。
线程同步的方式有两种:
方式1:同步代码块
方式2:同步方法
关键字:Synchronized(同步)
6.2.1同步代码块
在代码块声明上 加上synchronized
synchronized (锁对象) {
可能会产生线程安全问题的代码
}
注意:
1)同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
2)把对共享数据的操作括起来
3)锁对象:定义成成员变量
修改后:
public class MyTicker implements Runnable{
private int ticket=100; //100张票
private Object obj=new Object(); //锁对象
//线程任务
public void run() {
while(true){
synchronized (obj) {
if(ticket>0){
try {
Thread.sleep(10); //模拟资源被抢占
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票");
}
}
}
}
}
原理图:
当t0进来时,不管是否休眠,其他线程都不能进来,当一个线程进入数据操作时,无论是否休眠,其他线程只能等待。
6.2.2同步方法
在方法声明上加上synchronized
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步方法中的锁对象是 this
public class MyTicker2 implements Runnable {
private int ticket = 100; // 100张票 public synchronized void sale(){
//可能会产生线程安全的代码
if (ticket > 0) {
try {
Thread.sleep(10); // 模拟资源被抢占
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + ticket-- + "张票");
}
} // 线程任务
public void run() {
while (true) {
sale();
}
}
}
静态同步方法: 在方法声明上加上static synchronized
public static synchronized void method(){
可能会产生线程安全问题的代码
}
注意:
静态同步方法中的锁对象是 类名.class(本类字节码对象,以后学反射再了解)
静态不能访问非静态,所以要把成员变量也加上static
Tips:
1)同步怎么保证安全性?
没有锁的线程不能执行只能等待
2)加了同步,线程进入同步判断锁时,获取锁,释放锁,会导致运行速度下降
3)StringBuffer里面所有方法都加了关键字,所以慢,但安全
StringBuilder没有加,所以快,但不安全
总结:
单线程不会出现安全问题
只有多个线程共享同一个资源时,才会出现安全问题
6.3 Lock接口
Jdk1.5以后出现的用来替代synchronized
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
常用方法:
没有static,所以要用子类对象调用
锁对象就是Lock对象
private Lock lock=new ReentrantLock();
例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class MyTicker3 implements Runnable {
private int ticket = 100; // 100张票
// 创建Lock接口实现类对象
private Lock lock = new ReentrantLock(); // 线程任务
public void run() {
while (true) {
// 获取锁
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + ticket-- + "张票");
}
// 释放锁
lock.unlock();
}
}
}
6.4死锁
同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
死锁,两个线程互相等待
前提:必须是多线程之间出现的同步嵌套
代码示例:
public class LockA {
private LockA(){ }
public final static LockA locka=new LockA();
}
public class LockB {
private LockB(){ }
public final static LockB lockb=new LockB();
}
public class DeadLock implements Runnable{
private int i=0;
public void run(){
while(true){
if(i%2==0){
//先进A同步,再进B同步
synchronized (LockA.locka) {
System.out.println("if...locka");
synchronized (LockB.lockb) {
System.out.println("if...lockb");
}
}
}else{
//先进B同步,再进A同步
synchronized (LockB.lockb) {
System.out.println("else...lockb");
synchronized (LockA.locka) {
System.out.println("else...locka");
}
}
}
i++;
}
}
}
public class Demo01 {
public static void main(String[] args) {
DeadLock dl=new DeadLock();
Thread t0=new Thread(dl);
Thread t1=new Thread(dl);
t0.start();
t1.start();
}
}
死锁无法解决,只能避免。
6.5等待唤醒机制
6.5.1定义
线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
6.5.2等待唤醒机制所涉及到的方法
wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
注意:
1)所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在同步中才有效。
2)同时这些方法在使用时必须标明所属锁(锁对象调用方法),这样才可以明确出这些方法操作的到底是哪个锁上的线程。
代码示例:
public class Resource {
public String name; //为了演示方便,设为public
public int age;
//添加标记:true赋值完成,false输出完成
public boolean flag=false;
}
public class Input implements Runnable{
private Resource r;
public Input(){ }
public Input(Resource r){
this.r=r;
} //给Resource赋值
public void run() {
int i=0;
while(true){
//添加同步代码块
synchronized (r) {
//判断标记
if(r.flag){
try {
r.wait(); //要用锁调用
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(i%2==0){ //模拟交替进行,用奇偶数实现
r.name="张三";
r.age=18;
}else{
r.name="李四";
r.age=81;
}
r.flag=true; //赋值完成
r.notify(); //唤醒
}
i++;
}
}
}
public class Output implements Runnable{
private Resource r;
public Output(){ }
public Output(Resource r){
this.r=r;
} //对Resource输出
public void run() {
while(true){
//添加同步代码块
synchronized (r) {
//判断标记
if(!r.flag){
try {
r.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(r.name+"..."+r.age);
r.flag=false; //输出完成
r.notify(); //唤醒
}
}
}
}
public class Demo01 {
public static void main(String[] args) {
Resource r=new Resource();
Input in=new Input(r);
Output out=new Output(r);
Thread tin=new Thread(in);
Thread tout=new Thread(out);
tin.start();
tout.start();
}
}
注意:
1)要共享同一个资源,所以用构造方法,赋同一个对象r
2)Wait()和notify()要放在同步中,要用锁对象调用
3)实现交替进行:
用一个标记flag:
true:赋值完成
false:输出完成
Input:如果是true,等待;如果是false,赋值,将flag改为true
Output:如果是true,输出,将flag改为false;如果是false,等待
总结:
线程池:掌握
线程安全:了解
多线程----Thread类,Runnable接口,线程池,Callable接口,线程安全的更多相关文章
- 线程创建的三种方法:继承Thread类,实现Runnable接口,实现Callable接口
线程创建 三种创建方式 1. 继承Thread类 自定义线程类继承Thread类 重写run()方法,编写线程执行体 创建线程对象,调用start()方法启动线程 线程不一定执行,CPU按排调度 pa ...
- Java多线程系列--“JUC线程池”01之 线程池架构
概要 前面分别介绍了"Java多线程基础"."JUC原子类"和"JUC锁".本章介绍JUC的最后一部分的内容——线程池.内容包括:线程池架构 ...
- Java多线程系列--“JUC线程池”03之 线程池原理(二)
概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...
- Java多线程系列--“JUC线程池”02之 线程池原理(一)
概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...
- 【温故而知新-万花筒】C# 异步编程 逆变 协变 委托 事件 事件参数 迭代 线程、多线程、线程池、后台线程
额基本脱离了2.0 3.5的时代了.在.net 4.0+ 时代.一切都是辣么简单! 参考文档: http://www.cnblogs.com/linzheng/archive/2012/04/11/2 ...
- Java多线程系列--“JUC线程池”04之 线程池原理(三)
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...
- Java多线程系列--“JUC线程池”05之 线程池原理(四)
概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...
- 2.匿名类,匿名类对象,private/protected/public关键字、abstract抽象类,抽象方法、final关键字的使用,多线程Thread类start方法原理
package com.bawei.multithread; //注意:模板方法我们通常使用抽象类或者抽象方法!这里我们为了方便在本类中使用就没有使用抽象类/抽象方法 public class Tem ...
- C#多线程---线程池的工作者线程
一.线程池简介 创建和销毁线程是一个要耗费大量时间的过程,太多的线程也会浪费内存资源,所以通过Thread类来创建过多的线程反而有损于性能,为了改善这样的问题 ,.net中就引入了线程池. 线程池形象 ...
随机推荐
- select查询语句执行顺序
查询中用到的关键词主要包含六个,并且他们的顺序依次为select--from--where--group by--having--order by其中select和from是必须的,其他关键词是可选的 ...
- Java集合框架(1)
Collection接口:它是Java集合框架的一个根接口,也是List.Set和Queue接口的父接口.同时它定义了可用于操作List.Set和Queue的方法—增删改查. Map接口:它提供了一种 ...
- python3 + selenium + eclipse 中报:Unable to find a matching set of capabilities
在环境python3 + selenium + eclipse 运行报错::Unable to find a matching set of capabilities 解决办法:Update Fire ...
- java的动态代理原理
之前虽然会用JDK的动态代理,但是有些问题却一直没有搞明白.比如说:InvocationHandler的invoke方法是由谁来调用的,代理对象是怎么生成的,直到前几个星期才把这些问题全部搞明白了. ...
- May we can use Turbolinks or Pjax in our web apps
Turbolinks[1]: Turbolinks makes following links in your web application faster.Instead of letting th ...
- 4种方法实现C#无标题栏窗体的移动
首先C#无标题栏窗体的实现代码 在load时实现 无工具栏+无窗口标题 private void Form1_Load(object sender, EventArgs e) { this.Contr ...
- Process 启动参数问题
c#在有些情况下需要在启动另一个程序时传递参数,这里存在两个问题. 1.如果在参数里面含有空格,那么传递过去就会变成一个字符数组,这种情况是不满足情况的,解决方案是在传递参数时将空格用一些特殊字符替换 ...
- Poll: Most Americans&n…
Most Americans support tough new measures to counter gun violence, including banning assault weapons ...
- celery和supervisor配合使用,实现supervisor管理celery进程
在这里我选择redis作为celery异步任务的中间人,系统选择CentOS6.5 64位.redis.celery和supervisor的安装参见官方文档. 安装完毕后: 1, 创建celery的实 ...
- 第一个PyQuery小demo
1.打开网址https://www.v2ex.com/,查看其源码. 2.打开PyCharm编译器,新建工程c3-11,新建python file,命名为v2ex.py,同时,新建file,命名为v2 ...