线程,进程,程序的区别
程序(program):是为完成特定任务,某种语言编写的一组指令的集合.即指一段静态的代码,静态对象. 程序(process):是程序的一次执行过程,或是正在运行的一个程序.是一个动态的过程,由它自身的产生,存在,消亡的过程----生命周期.
运行的某个程序可以是一个进程,比如浏览器 程序是静态的,进程是动态的 进程作为资源分配的得,系统在运行时会为每个进程分配不同的内存区域
线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径.
若一个进程统一时间可以并行的执行多个线程,则就是支持多线程的.- 线程作为调度和执行得,每个线程拥有独立的运行栈和程序计数器,线程切换的开销较小 一个进程中的多个线程共享相同的内层单元/内存地址->它们从统一堆中分配对象,可以访问相同的变量和对象.这就使得线程间的通信更简便,高效.但多个线程操作共享的系统资源可能就会带来安全隐患.
单核,多核CPU
早期的CPU多是单核的,也就是一个核心,而如今经常听到16核心32线程的CPU,也就是如今的CPU的核心数增加了,那么单核于多核CPU有什么区别呢?
单核心CPU那么也就只有一个核心(core),那么同一时间,CPU只能执行一个线程.
那么为什么我们能在单核心CPU电脑上运行多个程序并且感觉他们是在同时运作的呢?
是因为CPU在这些线程之间进行高速的切换,CPU将给每个任务分配一个时间片(大概10ms),执行完这一个时间片CPU就会切换到下一个线程执行,只要CPU切换的速度够快就可以近似的认为这些线程在同时执行.因此单核CPU执行多线程其实并不是真正意义上的多线程,而是线程切换.因此单核CPU执行多线程总会有某个线程处于等待(wait)状态,只不过是看起来像同时运行而已.因此单核心CPU执行多线程反而运行速度更慢,但是即使这样,单核心CPU也依旧需要多线程是因为对于一些需要交互的程序,如果只是单线程,那么用户的体验极差(试想一下提前加载好的页面用起来舒服还是你点完必须要等待一下的舒服)
多核心CPU意味着有多个的核心,也就多个线程可以在多个CPU上被执行,只有在多核心CPU上的多线程才能称为真正意义上的多线程,多线程才能真正的发挥出真正的优势.
并行,并发
并行:多个CPU同时执行多个任务 也就是上面多核心CPU中所说的多个CPU同时被分配执行多个线程. 并发:一个CPU(采用时间片)同时执行多个任务 也就是上文说的单核心CPU在多个任务之间按分配时间片的方式在多个任务之间来回切换执行 多核心CPU既可以并发又可以发生并行,单核心CPU只能发生并发
Java中创建多线程的四种方法
概念了解差不多了就开始上代码吧 在jdk1.5之前,创建线程只有两种方法 1: 一个是将一个类声明为Thread的子类。 这个子类应该重写Thread类的run方法 然后可以分配并启动子类的实例。 2:另一种方法来创建一个线程是声明实现类Runnable接口。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动。 Thread 以下是官方API,同时也建议简单的看一眼Thread的结构(有惊喜)
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
. . .
}
}
Runnable
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
@Override
public void run() {
. . .
}
}
在一个类中重写好Runnable接口的run方法之后,就可以创建这个类的实例并且调用start方法.使用方法如下
public static void main(String[]args)
{
PrimeThread pt=new PrimeThread(10L);
pt.start();
PrimeRun pr=new PrimeRun(10L);
pr.start();
}
在jdk1.5之后,增加了两种创建线程的新方法 1:实现Callable接口并且重写其call方法.Callable接口类似于Runnable ,不过call方法不仅有一个泛型的返回值,还可以抛出Exception类型异常.因此如果需要有返回值或需要异常处理,可以考虑使用Callable. 2:线程池方法,目前使用的最多的一种Executors的工厂方法newFixedThreadPool(int nThread)将返回一个ExecutorService对象,这个对象有一个submit()方法可以传递实现Callable接口并重写了call方法的一个实例,并将执行call方法,或传递一个实现了Runnable接口并重写了run方法的一个实例.当方法体执行完毕后,并执行run方法.最后如果当前线程执行完毕任务我们需要回收线程池时,可以使用shutdown()方法关闭线程池.
Future submit(Callable task) 提交值返回任务以执行,并返回代表任务待处理结果的Future。 Future<?> submit(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的处理结果的Future。 void shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
Callable
package MultiThread_ch8.MultithreadingDemo.CallableandPool;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class UseCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 1;
}
}
public class CallableImp {
public static void main(String[] args) {
UseCallable c = new UseCallable();
FutureTask<Integer> ft=new FutureTask(c);
new Thread(ft).start();
try {
Integer obj=ft.get();
System.out.println(obj);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
ThreadPoolExecutor
package MultiThread_ch8.MultithreadingDemo.CallableandPool;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
class pool1 implements Runnable{
@Override
public void run() {}
}
public class ThreadPool {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
ThreadPoolExecutor tpe= (ThreadPoolExecutor) es;
System.out.println(es.getClass());
es.submit(new pool1());
es.shutdown();
es.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
for (int i = 0; i <= 100; i++) {
if(i%2!=0)
System.out.println(i);
}return null;
}
});
}
}
以上部分简单罗列了四种创建多线程的方式,多线程虽妙,但是一个也需要考虑到一些多线程程序有的问题.比如死锁,同步问题
同步问题
同步问题概述 同步问题的发生需要满足以下条件: 1:该程序是一个多线程程序 2:该程序中多个线程访问了一个共享数据 现在假设有一个账户,两个人分三次向这个账户转账,每次1000元,那么如果这是一个单线程程序,一定不会发生同步问题,那么如果是一个多线程程序,那就不一定了.
package MultiThread_ch8.MultithreadingDemo;
import java.util.concurrent.locks.ReentrantLock;
class account implements Runnable{
private static int money=0;
ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
money+=1000;
System.out.println(money);
}
}
}
public class LearnDemo {
public static void main(String[] args) {
account a= new account();
new Thread(a).start();
new Thread(a).start();
}
}
结果如上,很明显,由于多核心CPU同时执行了两个线程,因此汇款的动作同时执行了,那么同一时刻,1000元同时被存入了进去,例如都是在3000元的时候一起存了进去,就导致存了轮次但是只加了1000元,对于这种问题,我们是不允许存在的,因此Java提供了三中解决方法.
解决同步问题的三种方法
同步代码块 将可能发生同步问题的代码用一个{}包住并加上synchronized(同步锁/同步监视器),则这段代码块变为同步代码块,同步代码块同一时间只能允许持有同步锁的线程进入代码块,其他线程必须等待该线程执行完代码块并且释放同步锁之后才有可能进入同步代码块,否则一直处于阻塞状态. 同时需要注意 synchronized()的括号中的锁必须是所有的需要访问共享数据的线程所共享的一个对象,当然,括号内的锁可以是任何一个类对象,因此你直接写一个this,也没有问题,但是你得确保你的this是唯一一份的,
package MultiThread_ch8.MultithreadingDemo;
import java.util.concurrent.locks.ReentrantLock;
class account implements Runnable{
private static int money=0;
ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
synchronized (this) {
{
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
money+=1000;
System.out.println(money);
}
}
}
}
public class LearnDemo {
public static void main(String[] args) {
account a= new account();
new Thread(a).start();
new Thread(a).start();
}
}
同步方法 同步方法即在一个方法前加上一个synchronized修饰,这个方法就变为一个同步方法.同步方法的锁是所在类的Class对象 代码如下
package MultiThread_ch8.MultithreadingDemo;
import java.util.concurrent.locks.ReentrantLock;
class account implements Runnable{
private static int money=0;
private Object obj=new Object();
ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
addmoney();
}
}
public synchronized void addmoney(){
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
money+=1000;
System.out.println(money);
}
}
}
public class LearnDemo {
public static void main(String[] args) {
account a= new account();
new Thread(a).start();
new Thread(a).start();
}
}
重进入锁 ReenrantLock类用来设定重进入锁.
一个可重入互斥Lock具有与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。 void lock() 获得锁。 void unlock() 尝试释放此锁。
与同步代码块一样,这把重进入锁应该也是唯一的,否则会出现和同步代码块可能出现的问题一样的问题
package MultiThread_ch8.MultithreadingDemo;
import java.util.concurrent.locks.ReentrantLock;
class account implements Runnable{
private static int money=0;
private Object obj=new Object();
ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
lockaddmoney();
}
public void lockaddmoney(){
try{
lock.lock();
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
money+=1000;
System.out.println(money);
}
}finally {
lock.unlock();
}
}
}
public class LearnDemo {
public static void main(String[] args) {
account a= new account();
new Thread(a).start();
new Thread(a).start();
}
}
通过上面三个代码大概我们大概了解了解决同步问题的三种方法,接下来来了解什么是死锁. 什么是锁-知乎
死锁
“死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。”
例如某个线程现在手上有锁,然后它进入了一个if代码块,经判断后发现该锁持有者不符合要求,因此被wait()挂起,之后该对象释放锁,锁由另一个对象使用结果也不符合要求,循环往复,所有的对象都被挂起,没有对象使用notify()/notifyAll()方法,这样所有的线程全进入阻塞状态,程序既不会继续运行,也不会终止.只能认为手动关闭程序. 接下来是一个死锁的程序 两个线程共享两个StringBuilder(线程不安全),由于线程1获取锁s1后,线程2获取了s2,而线程1需要进入下一个代码块需要锁s2,线程2进入下一个代码块需要锁s2,因此互相等待对方释放锁,导致了死锁
public class DeathLock {
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
StringBuilder s2 = new StringBuilder();
new Thread() {
@Override
public void run() {
synchronized (s1) {
s1.append("a");
s2.append(1);
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s1.append("b");
s2.append(2);
System.out.println(s1.toString());
System.out.println(s2.toString());
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2) {
s1.append("c");
s2.append(3);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("d");
s2.append(4);
System.out.println(s1.toString());
System.out.println(s2.toString());
}
}
}
}).start();
}}
运行发现什么也不输出,并且程序也不终止,对于这种情况,我们无能为力,只能手动关闭,因此需要尽可能避免写出可能死锁的程序.
线程通信
线程通信,顾名思义,即线程之间交互,例如某个线程输出玩某条语句之后,通知另一个线程执行某条语句,常用方法如下:
wait():一旦执行当前方法,当前线程就进入阻塞状态,同时释放同步监视器 notify():一旦执行此方法,将随机唤醒一个被wait的线程,唤醒将按照线程的优先级来选择 notifyAll():执行此方法,唤醒所有的wait线程
- 说明:
- 上面的这三个方法是线程通信的方法,这些方法必须放在同步方法或同步代码块中才能使用
- 并且这三个方法的调用者必须是同步代码块的调用者(this)或当前方法(xxx.class)
否则出现IlleglMonitorStateException异常 - 这三个方法定义再java.lang.Object方法中,由于想要调用这个方法那么就得有这个方法,因此直接使用Object类对象来调用最为稳妥
- Reentrant不是用这三种方法来通信.
- sleep与wait的异同
- 同:都使当前线程进入阻塞状态
- 异:wait必须使用再同步代码块或同步方法中
- 他们都在同步代码中时wait会释放同步监视器(锁),而sleep不会
- sleep是thread类下的方法 而wait是object类下的方法
接下来是一个简单的程序: Baozi类
package MultiThread_ch8.MultithreadingDemo.BaoziDemo;
public class Baozi {
public String pi;
public String xian;
public boolean flag;
public Baozi(){}
public Baozi(boolean flag) {
this.flag = flag;
}
public Baozi(String pi, String xian) {
this.pi = pi;
this.xian = xian;
}
}
Baozipu类
package MultiThread_ch8.MultithreadingDemo.BaoziDemo;
public class Baozipu extends Thread{
private Baozi bz;
public Baozipu(Baozi bz)
{
this.bz=bz;
}
public void run() {
int count=0;
while (true)
{
synchronized(bz)
{
if(bz.flag)
{
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("需要制作包子等我一会");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count%2==0)
{
bz.pi="南瓜";
bz.xian="猪肉馅";
count++;
}else if(count%2==1)
{
bz.pi="冬瓜";
bz.xian="鸭肉";
count++;
}
System.out.println("包子做好了"+bz.pi+bz.xian+"的包子");
bz.flag=true;
bz.notify();
System.out.println("提示客人去吃");
}
}
}
}
Chihuo类
package MultiThread_ch8.MultithreadingDemo.BaoziDemo;
public class Chihuo extends Thread{
private Baozi bz;
public Chihuo(){}
public Chihuo(Baozi bz)
{
this.bz=bz;
}
@Override
public void run() {
while (true)
{
synchronized (bz)
{
if(bz.flag)
{
System.out.println("正在吃"+bz.pi+bz.xian+"包子");
System.out.println("包子吃完了,在做一个");
System.out.println("------------------------");
bz.flag=false;
bz.notify();
}
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Baozi bz=new Baozi(false);
new Baozipu(bz).start();
new Chihuo(bz).start();
}
}
运行可以发现程序不断运行且不终止,可以多实现几个线程来互相交互,方式一样
|