Java多线程基础

多线程

进程

  • 进程是操作系统中运行的一个任务,一个应用程序运行在哟个进程中。

  • 进程(process)是一块包含了某些资源的内存区域,操作系统利用进程把他的工作划分为一些内存单元。称为线程(thread).

  • 进程还拥有虚拟地址空间,该空间仅能被他包含的线程访问。

  • 线程只能归属一个进程并且他只能该进程所拥有的资源。当操作系统创建一个进程后,该进程会自动申请一个名为主线程或首要线程的进程。

线程使用场合

  • 线程通常用于在一个程序中需要同时完成多个任务的情况,我们可以将每个任务定义一个线程,是的他们得以一同工作。
  • 也可以用于在单一线程下可以完成,但是用多线程可以更快的情况下,比如文件下载。

并发原理

  • 多个线程“同时”运行只是我们感官上的一种表现,事实上线城是并发运行的,OS将时间划分为很多时间片段(时间片),尽可能的均匀的划分给每一个线程,获取时间片段的线程被CPU运行,而其他线程全部等待。-所以微观上是走走停停的,宏观上都在运行。这种现象叫并发,但是不是绝对意义上的“同时发生”。

线程状态

线程状态

线程案例

案例一:

public class ThreadDemo {
		public static void main(String[] args) {
			Thread t1 = new MyThread1();
			Thread t2 = new MyThread2();
			t1.start();
			t2.start();
		}
}
class MyThread1 extends Thread{
	public void run() {
		for(int i = 0;i<1000;i++) {
			System.out.println("你是谁啊?");
		}
	}
}
class MyThread2 extends Thread{
	public void run() {
		for(int i = 0;i<1000;i++) {
			System.out.println("我是查水表的!");
		}
	}
}

启动线程要指定start方法,而不是直接调用run方法,run方法是线程要执行任务,当线程的start方法被调用后,线程进入runable状态,一旦获取cpu时间,run方法会自动被调用。

  • 以上创建线程有两个不足:
  1. 由于java是单继承,那么当继承了Thread后就无法在继承其他类。
  2. 由于继承了Thread后重写run方法规定了线程执行的任务,这导致线程于任务有一个必然的耦合关系,不利于线程的重用。

案例二

package thread;

public class ThreadDemo1 {
   	public static void main(String[] args) {
   		Runnable r1 = new MyRunnable1();
   		Runnable r2 = new MyRunnable2();
   				Thread t1 =new Thread(r1);
   				Thread t2 =new Thread(r2);
   				t1.start();
   				t2.start();
   	}
}
class MyRunnable1 implements Runnable{
   public void run() {
   	for(int i = 0;i<1000;i++) {
   		System.out.println("你是谁啊?");
   	}
   }

   
}
class MyRunnable2 implements Runnable{
   public void run() {
   	for(int i = 0;i<1000;i++) {
   		System.out.println("我是查水表的!");
   	}
   }
}

案例二很好的解决了案例以所产生的问题

用匿名内部类创建多线程

暂略

Thread.currendThread方法

  • Thread的静态方法currendThread方法可以用于获取当前代码片段的线程。
  • Thread current = Thread.currentThread();
Thread current = Thread.currentThread(); //需要查看那个进程给那个进程加
获取线程信息
  • 获取线程相关信息的相关方法
long getId();               //返回该线程的标识符
String getName();           //返回该线程的名称
int getPriority();          //返回线程的优先级   范围:1-10.其中1最低,10最高 
Thread.state getState();    //获取线程状态
boolean isAlive();          //测试线程是否处于活动状态
boolean isDaemon();         //测试线程是否为守护线程
boolean isInterrupted();    //测试线程是否已经中断
线程优先级

线程的时间片分配完全听线程调度的,线程只能被动的被分配时间,对线程调度工作不能干预,但是可以通过提高线程的优先级来达到尽可能干预的目的,理论上,优先级越高的线程,获取CPU时间片的次数就越高。

sleep方法
  • Thread的静态方法sleep用于使当前线程进入阻塞状态:
static void sleep(long ms)
  • 该方法会使当前线程进入阻塞状态指定毫秒,当阻塞指定毫秒后,当前前程会重新进入Runnable状态,等待分配时间片。
  • 该方法声明抛出一个InteruptException。所以在使用该方法时需要捕获这个异常。

实例

public class Lock {
		public static void main(String[] args) {
			Thread t1 =new Thread(new MyRunnable());
			t1.start();
			}			
}
class MyRunnable implements Runnable{
public void run() {
	while(true) {
	SimpleDateFormat dfs = new SimpleDateFormat("yyy年MM月dd日---HH:mm:ss:SS毫秒"); 
	String tim = dfs.format(Calendar.getInstance().getTime());
	System.out.println(tim);
	try {
		Thread.sleep(1000);         //sleep方法
	} catch (InterruptedException e) {
		// TODO 自动生成的 catch 块
		e.printStackTrace();
	}
	}
}
}
守护进程
  • 守护进程与普通线程在表象上没有生么区别,我们只需要通过Thread提供的Thread方法来设定即可:
void setDaemon(boolean) //当参数为true时该线程为守护进程。
  • 守护线程的特点是,当进程中只剩下守护进程时,所有守护进程强制终止。
  • GC就是运行在一个守护线程上。

实例

public class ThreadDemo3 {
		public static void main(String[] args) {
			Thread rose = new Thread() {
			public void run() {
				for(int i=0;i<10;i++) {
					System.out.println("rose:我要跳了");
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO 自动生成的 catch 块
						e.printStackTrace();
					}
				}
			}
		};
		Thread jack = new Thread() {
			public void run() {
				for(int i=0;i<1000;i++) {
					System.out.println("jack:你跳我也跳!");
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO 自动生成的 catch 块
						e.printStackTrace();
					}
				}
			}
		};
		rose.setPriority(4) ;
		jack.setDaemon(true);   //设置jack为守护进程,当rose进程结束,jack进程也跟着结束!!!
		rose.start();
		jack.start();
}
}
Thread的静态方法:yield()
static viod yield()

该方法用于使当前线程主动让出当此CPU时间回到Runnable状态,等待分配时间按片。

join()方法
  • join方法可以使调用该方法的线程进入阻塞状态,直到该方法所属线程完成工作才会解除调用该方法的阻塞状态。

  • join方法一般用来完成多个线程之间的同步工作问题(两个线程有先后顺序)。

实例

public static Boolean isFinish = false;
				public static void main(String[] args) {
					final Thread download = new Thread() {
						public void run() {
							System.out.println("开始下载:");
							for(int i=0;i<100;i++) {
								System.out.println("down:"+i+"%");
								try {
									Thread.sleep(60);
								} catch (InterruptedException e) {
									// TODO 自动生成的 catch 块
									e.printStackTrace();
								}
								isFinish = true;
							}
							System.out.println("下载完毕!");
						}
					};
					Thread show = new Thread() {
					public void run() {
						System.out.println("show:开始显示图片");
						try {
							download.join(); //这里卡住,等待download完成在进行下面的步骤。
						} catch (InterruptedException e) {
							// TODO 自动生成的 catch 块
							e.printStackTrace();
						}  //这里    
						
						if(!isFinish) {
							throw new RuntimeException("图片没有下载完毕");
						}
						System.out.println("图片显示完毕");
					}};
					download.start();
					show.start();
					}
}
线程同步
  • 多个线程并发读写一个临界资源时会发生“线程并发安全问题”

  --多线程共享实列变量

  --多线程共享静态公共变量

  • 若想解决线程安全问题,需要将异步的操作变为同步的操作

  --异步操作:多线程并发的操作,相当于各干各的。

  --同步操作:有先后顺序的操作,你干完了我在干。

synchronized关键字是java中的同步锁

多线程并发访问同一资源时,会形成“抢”的现象。由于线程切换时机不确定性,可能导致代码执行顺序的混乱,严重时会导致系统瘫痪。

实列

public class SyncDemo {
	public static void main(String[] args) {
		final Table table = new Table();
		Thread t1 = new Thread() {
			public void run() {
				while(true) {
					int bean = table.getBean();
					System.out.println(getName()+":"+bean);
				}
			}
		};
		Thread t2 = new Thread() {
			public void run() {
				  while(true) {
					int bean = table.getBean();
					Thread.yield();

					System.out.println(getName()+":"+bean);
				}
			}
		};
		t1.start();
		t2.start();
		
	}
		
}
class Table{
	private int beans = 20;
	public  synchronized int getBean() {        //安全锁,保证线程安全,
		if(beans ==0) {
			throw new RuntimeException("没有豆子了");
		}
		Thread.yield();
		return beans--;
	}
}

当方法被synchronized修饰后后,该方法为同步方法,即:多个线程不能同时进入方法内部执行。
遂于成员方法而言,synchronized会在一个线程调用该方法是将该方法所属对象加锁,其他线程在执行该方法时由于执行方法的线程没有被释放锁,所以只能在方法外阻塞,
直到持有方法锁的线程将方法执行完毕。所以解决多线程并发执行的安全问题是将“抢”变为“排队”。

同步代码块

同步块可以要求对各线程对该块内的代码排队执行,但是前提条件是同步监视器对象(即上锁的对象)要求多个线程看到的必须是同一个。

synchronized(同步监视器对象){
    需要同步的代码
}
  • 同步执行:多个线程必须排队执行
  • 异步执行:多个线程可以同时执行

案例

public class SyncDemo2 {
	public static void main(String[] args) {
		final Shop shop = new Shop();
		Thread t1 = new Thread() {
			public void run() {
			shop.buy();
				}
			};
		Thread t2 = new Thread() {
			public void run() {
			shop.buy();
				}
			};
		t1.start();
		t2.start(); 
	} 
}
class Shop{
public void buy() {
	Thread t = Thread.currentThread();
	try {
		System.out.println(t.getName()+"正在挑衣服···");
		Thread.sleep(5000);
		/*
		* 加锁的同步块 
		*/
		synchronized(this) {
		System.out.println(t.getName()+"正在试衣服···");
		Thread.sleep(5000);
		}
		
		System.out.println(t.getName()+"结账离开···");
	    }catch(Exception e){
		e.printStackTrace();
	}
    }
}
静态方法的同步

当一个静态方法被synchronized修饰后,那么该方法就是同步方法,由于静态方法从属类,全局就一份,所以同步的静态方法一定具有同步效果。与对象无关

实列

package thread;

public class SyncDemo3 {
		public static void main(String[] args) {
			Thread t1 =new Thread() {
				public void run() {
					Foo.dosome();
				}
			};
			Thread t2 =new Thread(){
				public void run() {
					Foo.dosome();
				}
			};
			t1.start();
			t2.start();
		}
}
class Foo{
	public static synchronized void dosome() {
		try {
			Thread t= Thread.currentThread();
			System.out.println(t.getName()+"方法正在执行");
			Thread.sleep(5000);
			System.out.println(t.getName()+"方法执行完毕");
			
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

如果个上面的代码创建两个对象,调取同一个静态方法,两个对象都是静态的,和对象无关.

实列

package thread;

public class SyncDemo4 {
		public static void main(String[] args) {
			final Foo f1 =new Foo();
			final Foo f2 =new Foo();
		
			Thread t1 =new Thread() {
				public void run() {
					f2.dosome();  //与对象无关
				}
			};
			Thread t2 =new Thread(){
				public void run() {
					f1.dosome(); //与对象无关
				}
			};
			t1.start();
			t2.start();
		}
}
class Foo{
	public static synchronized void dosome() {
		try {
			Thread t= Thread.currentThread();
			System.out.println(t.getName()+"方法正在执行");
			Thread.sleep(5000);
			System.out.println(t.getName()+"方法执行完毕");
			
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}
互斥锁

synchronized也叫互斥锁。

使用synchronized修饰对多段代码,只要他们的同步监视器对象相同,那么这几段代码就是互斥关系,多个线程不能同时执行这些代码

实列

package thread;

public class ThreadDemo5 {
   public static void main(String[] args) {
   final	Boo boo= new Boo();
   	Thread t1 = new Thread() {
   		public void run() {
   			boo.MEthodA();
   		}
   	};
   	Thread t2 = new Thread(){
   		public void run() {
   			boo.MEthodB();
   		}
   	};
   	t1.start();
   	t2.start();
   }

}
class Boo{
   public synchronized void MEthodA() {
   	try {
   	Thread t = Thread.currentThread();
   	System.out.println(t.getName()+":正在执行A方法");
   	Thread.sleep(5000);
   	System.out.println(t.getName()+":A方法执行完毕");
   }catch(Exception e) {
   	e.printStackTrace();
   }
   	}
   public synchronized void MEthodB() {
   	try {
   	Thread t = Thread.currentThread();
   	System.out.println(t.getName()+":正在执行B方法");
   	Thread.sleep(5000);
   	System.out.println(t.getName()+":B方法执行完毕");
   }catch(Exception e) {
   	e.printStackTrace();
   }
   	}
}

锁A与锁B锁的是同一个对象,就达到了互斥的效果

线程同步
  • Srting Buffer是同步的 synchronized append();

  • String Builder不是同步的append();

  • Vector和Hashtable是同步的;

  • ArrayList和hashMap不是同步的

  • 获取线程安全的集合方式:

Collections.synchronizedList();  //获取线程安全的List集合
Collections.synchronizedMap();  //获取线程安全的Map

实列

package thread;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 将集合或Map转换成线程安全的
 * @author ak_tjh
 *
 */
public class SyncDemo5 {
	public static void main(String[] args) {
		 List<String> list= new ArrayList<String>();
		 list.add("one");
		 list.add("two");
		 list.add("three");
		 list.add("four");
		 System.out.println("不安全:"+list);
		 list = Collections.synchronizedList(list);  //将不安全的线程转换称为安全的线程
		 System.out.println("安全:"+list);
		 
		 
		 Set<String> set = new HashSet<String>(list);//将不安全的线程转换称为安全的线程
		 set = Collections.synchronizedSet(set);
		 System.out.println(set);
		 
		 Map<String,Integer> map = new HashMap<String,Integer>();
		 map.put("语文", 99);
		 map.put("英语", 98);
		 map.put("数学", 95);
		 map = Collections.synchronizedMap(map);;//将不安全的线程转换称为
	}
}

API手册上有说明,就算安全的集合那么其中对于元素的操作,如add、remove等方法都不予迭代器遍历做互斥,需要自行维护互斥关系。

使用ExecutorService实现线程池
  • ExecutorService是java提供的用于管理线程池的类。
  • 线程池主要有两个作用:

  --控制线程数量

  --重用线程

  • 当一个程序中若创建大量线程,并在任务结束后销毁,会给系统带来过度资源消耗,以及过度切换线程的危险,从而导致系统崩溃。用线程池可以解决这个问题。
    线程池

实列

package thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
		public static void main(String[] args) {
			ExecutorService threadPool = Executors.newFixedThreadPool(2);
			
			for(int i=0;i<5;i++) {
				Runnable runn= new Runnable() {

					public void run() {
						new Thread();
						Thread t = Thread.currentThread();
						
						try {
							System.out.println(t+"正在执行任务");
							Thread.sleep(1000);
							System.out.println("任务执行完毕");
						} catch (Exception e) {
							// TODO: handle exception
						}
					}
				};
				threadPool.execute(runn);
				System.out.println("指派了一个任务交给线程池");
			}
			threadPool.shutdown();		//任务完成了再停
			System.out.println("线程停了");
		}
}
双缓冲队列
  • BlockQueue是双缓冲队列。
  • 在对线程并发时,若需要使用队列,我们可以使用Queu,但是要解决一个问题就是同步,但同步操作会降低并发对Queue操作的效率。
  • BlockQueue内部使用两条队列,可允许两个线程同时向队列一个做存储,一个做取出操作。在保证并发安全的同时提高了队列的存取效率。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值