多线程
进程
-
进程是操作系统中运行的一个任务,一个应用程序运行在哟个进程中。
-
进程(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方法会自动被调用。
- 以上创建线程有两个不足:
- 由于java是单继承,那么当继承了Thread后就无法在继承其他类。
- 由于继承了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内部使用两条队列,可允许两个线程同时向队列一个做存储,一个做取出操作。在保证并发安全的同时提高了队列的存取效率。