关于线程的概念
1、程序一般是指一段静态代码,静态对象;进程是程序的一次执行过程或正在运行的程序动态的的过程,有它的产生、存在和消亡的过程;线程是指一个程序内部的一条执行路径,也就是说每一个任务就代表一个线程。进程与线程相互联系然而又有区别,从本质上来说每个进程都有自己的一整套变量,而线程间可以共享变量,共享变量使得线程之间的通信比进行之间通信更有效、更容易。
2、并不是每种情况都需要多线程,当程序需要同时执行两个或多个任务;当程序需要实现一些等待任务,如用户的输入,文件读写,网络操作,搜索;当程序需要运行一些后台程序。在这些情况下我们需要使用多线程。
3、线程的生命周期:每个线程都有新建、就绪、运行、阻塞、死亡这五种状态
创建线程的两种方式
1、继承Thread类:
a.创建一个类继承Thread;b.重写run()方法,将要执行的业务逻辑代码放在run()方法体中;c.在主类中创建子线程类的实例;d.调用线程方法start(),将此线程变为就绪状态。
package com.thread;
public class TestThread {
public static void main(String [] args){
//3、创建一个子类对象
SubThread st=new SubThread();
//4、调用线程的start()方法,开始启动线程;调用相应的run()方法
st.start();
//这个是主线程的方法
for(int i=1;i<=100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
//1、创建一个继承Thread的子类
class SubThread extends Thread{
//2、重写run()方法
@Override
public void run() {
for(int i=1;i<=100;i=i+2){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
2、实现Runnable接口
a.创建一个类实现Runnable接口;b.重写run()方法,将业务逻辑代码放入在run()方法中;c.创建一个Runbable接口实现类的对象的实例 d.将这个对象作为形参传递给Thread类的构造器中,创建Thread类对象,然后再启动线程start()方法。
package com.thread;
public class ThreadTest {
public static void main(String[] args) throws Exception{
MyRunnable r=new MyRunnable();//c.创建一个Runbable接口实现类的对象的实例
Thread t=new Thread(r);//d.将这个对象作为形参传递给Thread类的构造器中,创建Thread类对象,然后再启动线程start()方法。
t.setName("自定义线程:");
t.start();
System.out.println(Thread.currentThread().getName());
}
}
class MyRunnable implements Runnable{//a.创建一个类实现Runnable接口
@Override
public void run() {//b.重写run()方法,将业务逻辑代码放入在run()方法中
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
3、这两种方式创建线程的异同在于a.Thread类其实也是实现Runnable接口;b.实现Runnable接口的方式要优于继承Thread类,首先实现的方式避免java中单继承的缺点,其次如果多个线程操作同一份数据资源,更加适合用实现的方式
线程中Thread类常用的方法
1、start():启动线程并执行相应的run方法
2、Thread.currentThread() Thread.currentThread().getName()、Thread.currentThread().setName("设置的name") 获取当前线程/获取当前线程的名字/设置当前线程的名字
3、sleep(long time):显式的让线程休眠,其中time以毫秒为单位
4、yield():让调用这个方法的线程释放当前cpu的执行权利
5、线程通信的方法:wait():另当前线程挂起并放弃cpu、同步资源使别的线程可以访问并修改共享资源,而当前线程排队等待再一前次对资源的访问;
notify():唤醒正在排队等待同步资源的线程中优先级别最高者结束等待;
notifyAll():唤醒所有正在排队等待资源的线程结束等待。
6、join():在A线程中调用B线程的Join()方法,表示强制执行B线程,这个时候A线程停止执行,直到B执行完毕A才可以继续执行。
线程的安全问题
一、线程安全问题的原因:由于一个线程正在操作共享数据,未执行完毕的情况下,另外一个线程参与进来,导致共享数据在安全上出了问题。如银行的转账存款业务,卖票业务等等。
二、解决线程安全的办法
1、同步块synchronized:将需要同步的业务逻辑代码放在synchronized(this){ ...}这个同步块中
2、同步方法 synchronized:将需要同步的方法加上关键字:synchronized如publicsynchronized void set(String name){...}
package com.thread;
public class Demo {
public static void main(String[] args) {
sell_Window sell=new sell_Window();
Thread t1=new Thread(sell);//开启三个窗口同时卖票
Thread t2=new Thread(sell);
Thread t3=new Thread(sell);
t1.start();
t2.start();
t3.start();
}
}
class sell_Window implements Runnable{//实现Runnable接口,把买票的业务逻辑放在run()方法中
int num=100;
@Override
public void run(){
while(true){
synchronized(this) {//利用同步块来实现线程安全,this表示当前类,我们也可以使用一个对象来做锁对象,其中利用</span><span style="font-size:12px;">Demo.class字节码也是比较好的
if(num>0){
try{
Thread.sleep(100);
System.out.println("窗口 "+Thread.currentThread().getName()+":售第"+(num--)+"张票");
}catch (Exception e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
2、同步方法 synchronized:将需要同步的方法加上关键字:synchronized如publicsynchronized void set(String name){...}
package com.thread;
public class Demo {
public static void main(String[] args) {
sell_Window sell=new sell_Window(new SellTicket());
Thread t1=new Thread(sell);//开启三个窗口同时卖票
Thread t2=new Thread(sell);
Thread t3=new Thread(sell);
t1.start();
t2.start();
t3.start();
}
}
class SellTicket{//共享数据区,以及买票的方法
int num=100;
public synchronized void sell(){//同步方法
if(num>0){
System.out.println("窗口 "+Thread.currentThread().getName()+":售第"+(num--)+"张票");
}else{
Thread.currentThread().stop();
}
}
}
class sell_Window implements Runnable{//实现Runnable接口,把买票的业务逻辑放在run()方法中
SellTicket sellTicket;
public sell_Window(SellTicket sellTicket) {
this.sellTicket=sellTicket;
}
@Override
public void run(){
while(true){
try{
Thread.sleep(100);
sellTicket.sell();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
3、锁Lock,在线程同步中的Lock其实功能上和synchronized有着异曲同工的效果,不过Lock可以提供更加细度的控制,在java中使用Lock锁的方式如下:
设计一个简易的cache系统:
package com.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*线程同步中的锁Lock */
public class ThreadLock {
public static void main(String[] args) {
new ThreadLock().init();
}
public void init(){
final Outputer outputer=new Outputer();
new Thread(new Runnable() {//匿名线程
@Override
public void run() {//run()方法
while(true){
try {
Thread.sleep(10);
outputer.output("zhangxiaoxiang");//执行的任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(10);
outputer.output("admin");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
static class Outputer{
Lock lock=new ReentrantLock();//获取锁
public void output(String name) {
int len=name.length();
lock.lock();//加锁
try{
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();//释放锁
}
}
}
}
在Lock中存在比较有用的两种锁,读写锁(ReadWriteLock),使用这样的锁可以保证读文件的时候可以并发,读写和写文件不可以并发。下面用一个比较实用的例子来说明读写锁的优点。
设计一个简易的cache系统:
package com.thread;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/*设置缓存系统:
* 多个人进行读取数据时,可以并行,所以这时候只需要读锁;当有人发现没有数据时,这时候需释放掉
* 读锁加上一个写锁,在写的时候不能被读取,写完之后就还原成读锁让其它人可以读取
* */
public class Thread_CacheDemo {
private Map<String,Object> cache=new HashMap<String,Object>();
public static void main(String[] args) {
}
//得到数据
private ReadWriteLock rwl=new ReentrantReadWriteLock();
public Object getData(String key){//多线程读取时
rwl.readLock().lock();//读锁
Object value=null;
try{
value=cache.get(key);
if(value == null){
rwl.readLock().unlock();//释放读锁
rwl.writeLock().lock();//写锁
try {
if(value == null){
value="aaaa";//这里实际上是查询数据库
}
} catch (Exception e) {
}finally{
rwl.writeLock().unlock();//释放写锁
rwl.readLock().lock();//加上读锁
}
}
}catch (Exception e) {
e.printStackTrace();
}finally{
rwl.readLock().unlock();//释放读锁
}
return value;
}
}
线程间通信
1、线程通信的概念:线程通信是指线程间相互交流的过程,一个线程可以释放自己的cpu权利来给其它被唤醒的线程使用。
2、线程通信的方法,线程通信主要用到了wait(),notif(),notifyAll(),线程通信的一个经典案例就是生产者和消费者调度算法。下面是生产者和消费者调度算法的实现过程:
package com.thread;
/*生产者与消费者的问题
* 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品
* ,店员一次只能持有固定数量的产品(比如20),如果生产者试图生 产更多的产品,店员会叫生产者停一下,
* 如果店中有空位放产品了再通知生产者继续生产;如果店中没产品了,店员会告诉消费者等一下,如果店中有了产品再
* 通知消费者。
* 1、多线程:生产者,消费者
* 2、共享数据(资源):产品的数量
* 3、需要线程安全
* 4、需要线程通信:生产者与消费者通信
* */
public class Produce_consume {
public static void main(String[] args) {
Clerk clerk=new Clerk();//共享数据
Producer p=new Producer(clerk);//生产者
Consumer c=new Consumer(clerk);//消费者
Thread t1=new Thread(p);//生产者线程
Thread t3=new Thread(p);//生产者线程
Thread t2=new Thread(c);//消费者线程
t1.setName("生产者1");
t2.setName("消费者");
t3.setName("生产者2");
t1.start();
t2.start();
t3.start();
}
}
//生产者
class Producer implements Runnable{
Clerk clerk;
public Producer(Clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
System.out.println("生产者开始生产产品");
while(true){
try{
Thread.currentThread().sleep(10);
clerk.addProduct();//生产者生产产品
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Consumer implements Runnable{
Clerk clerk;
public Consumer(Clerk clerk){
this.clerk=clerk;
}
@Override
public void run(){
System.out.println("消费者消费产品");
while(true){
try {
Thread.currentThread().sleep(10);
clerk.consumeProduct(); //消费者消费产品
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
//店员,这是一个共享的数据,供生产者和消费者使用
class Clerk{
int product;
public synchronized void addProduct(){
//生产产品
if(product>=20){
try{
//产品数大于或等于20,暂停生产
wait();//释放cpu资源
} catch (InterruptedException e){
e.printStackTrace();
}
}else{
product++;
System.out.println(Thread.currentThread().getName()+":生产了第"+product+"个产品");
notifyAll();//一旦有产品生产,去唤醒消费者进行消费,notifyAll()唤醒其它线程
}
}
public synchronized void consumeProduct(){
//消费产品
if(product<=0){
try {
wait();//产品数少于或等于0,停止消费
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println(Thread.currentThread().getName()+":消费了第"+product+"个产品");
product--;
notifyAll();//有消费,就去唤醒生产者进行生产
}
}
}
线程范围的共享数据ThreadLocal类
1、ThreadLocal类表示可以存放当前线程的变量,同时这个变量可以让这个线程中的所有对象共享。这个类的底层实现方式和HashMap()大致相同,它分别有get(),set(),remove(),setInitialValue()四个方法,这四个方法分别表示得到当前线程相关的变量、设置当前线程的相关变量、把与当前线程有关的变量全部remve、设置初始化的值。当我们遇到需要在一个线程中放置多个变量的情况时,可以把这些变量封装成一个对象来存储。
package com.thread;
import java.util.Random;
public class ThreadLocal_fun {
private static ThreadLocal<Integer> x=new ThreadLocal<Integer>();//表示放置一个变量
private static ThreadLocal<MyThreadScopeDate> xObj=new ThreadLocal<MyThreadScopeDate>();//放置一个变量对象
public static void main(String[] args) {
for(int i=0;i<2;i++){//开启两个线程
new Thread(new Runnable() {
@Override
public void run() {
int data=new Random().nextInt();
System.out.println(Thread.currentThread().getName()+" has put data:"+data);
x.set(data);
MyThreadScopeDate.getThreadInstance().setName("admin");//为每个线程放置变量
MyThreadScopeDate.getThreadInstance().setAge(data);
new A().get();
new B().get();
}
}).start();
}
}
static class A{
public void get(){
int data=x.get();
System.out.println("A:"+Thread.currentThread().getName()+" has put data:"+data);
MyThreadScopeDate obj=MyThreadScopeDate.getThreadInstance();
System.out.println("A"+Thread.currentThread().getName()+":"+obj);
}
}
static class B{
public void get(){
int data=x.get();
System.out.println("B:"+Thread.currentThread().getName()+" has put data:"+data);
MyThreadScopeDate obj=MyThreadScopeDate.getThreadInstance();
System.out.println("A:"+Thread.currentThread().getName()+":"+obj);
}
}
}
//封装的变量对象 ,单例
class MyThreadScopeDate{
private MyThreadScopeDate(){}//私有化的构造函数不允许实例化
private static ThreadLocal<MyThreadScopeDate> map=new ThreadLocal<MyThreadScopeDate>();
public static MyThreadScopeDate getThreadInstance(){
MyThreadScopeDate instance=map.get();
if(instance == null){
instance=new MyThreadScopeDate();
map.set(instance);
}
return instance;
}
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "MyThreadScopeDate [name=" + name + ", age=" + age + "]";
}
}
线程池
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力;一般来说可以创建三种不同类型的线程池,同时线程池也为我们提供了关闭的方法:
a.创建固定大小 的线程池:一次只能执行三个任务,其他任务等待。 Executors.newFixedThreadPool(3)
b.创建缓存线程池:线程数动态量化,空闲线程自动回收。 Executors.newCachedThreadPool()
c.创建单一线程池:池中只有一个线程,如果线程意外终止就新产生一个。Executors.newSingleThreadExecutor()
d.shutdown();//当所有的任务都运行完后,就关闭线程池
e.shutdownNow();//立刻关闭线程池
package com.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
public static void main(String[] args) {
//ExecutorService threadPool=Executors.newFixedThreadPool(3);//创建固定大小的线程池,这里表明只能同时运行3个线程。
//ExecutorService threadPool=Executors.newCachedThreadPool();//创建缓存线程池,为所有的任务都分配一个线程,自动关闭空闲的线程
ExecutorService threadPool=Executors.newSingleThreadExecutor();//创建单一线程池。
for(int i=1;i<10;i++){//循环往线程池中放10个任务
final int task=i;
threadPool.execute(new Runnable() {//在线程池中放任务
@Override
public void run() {
for(int j=5;j<10;j++){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程"+Thread.currentThread().getName()+" loop of"+j+"次,在执行第 "+task+"个任务!");
}
}
});
}
System.out.println("提交了10个任务");
//threadPool.shutdown();//当所有的任务都运行完后,就关闭线程池
//threadPool.shutdownNow();//立刻关闭线程池
}
}