程序&进程&线程
-
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
-
进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域 -
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的,线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
一个进程中的多个线程共享相同的内存单元/内存地址空间
它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
多线程创建的两种方法
一、创建Thread类的子类对象
- 创建一个继承于 Thread 类的子类
- 重写 Thread 类的 run()
- 创建 Thread 类的子类的对象
- 通过此对象调用 start()
注意:
直接调用 run() 方法不是多线程
一个已经 start() 的线程不能再启动一个 start()
public class OtherTest {
public static void main(String[] args) {
MyThread myThread = new MyThread("分线程");
myThread.start();
Thread.currentThread().setName("主线程");
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":\t" + Thread.currentThread().getPriority() + ":\t" + i);
}
}
}
}
class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":\t" + Thread.currentThread().getPriority() + ":\t" + i);
}
}
}
}
二、实现Runnable接口
- 创建一个实现了Runnable接口的类
- 实现类去实现Runnable的抽象方法:run()
- 创建实现类的对象
- 将实现类的对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start():①启动线程②调用当前线程的run()–>调用的runnable类型的target的run()
public class OtherTest {
public static void main(String[] args) {
Windows windows = new Windows();
Thread t1 = new Thread(windows);
Thread t2 = new Thread(windows);
Thread t3 = new Thread(windows);
t1.setName("售票处1:");
t2.setName("售票处2:");
t3.setName("售票处3:");
t1.start();
t2.start();
t3.start();
}
}
class Windows implements Runnable {
// 不需要使用static修饰,因为t1,t2,t3都是用该类的同一对象创建的
private int ticket = 100;
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出号码为" + ticket + "的票");
ticket--;
} else {
break;
}
}
}
}
Thread类的常用方法
1. start()//启动当前线程,调用当前线程的run()
2. run()//通常需要重写Thread类的此方法,将创建的线程要执行的操作声明在此方法中
3. currentThread()//静态方法,返回执行当前代码的线程
4. getName()//获取当前线程的名称
5. setName()//设置当前线程的名称
6. Thread.currentThread().setName("主线程");//设置主线程的名称
7. yield()//释放当前CPU的执行权,但可能CPU又会将执行权分配给当前线程
8. join()//在线程a中调用线程b的join(),此时线程a就进入阻塞状态,只有等线程b完全执行完以后才执行线程a
9. sleep()//让当前线程“睡眠”指定的millitime毫秒,在指定的millitime毫秒时间内,当前线程是阻塞状态
10.isalive()//判断当前线程是否存活
线程的优先级
MAX_PRIORITY:10 //最高的优先级
MIN _PRIORITY:1 //最低的优先级
NORM_PRIORITY:5 //一般情况下的默认优先级
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级
线程的同步
1、同步代码块
synchronized (同步监视器(锁)) {
// 需要被同步的代码(操作共享数据的代码)
}
// 任何对象都可以充当锁(要求:所有线程都共用同一把锁)
public class OtherTest {
public static void main(String[] args) {
Windows windows = new Windows();
Thread t1 = new Thread(windows);
Thread t2 = new Thread(windows);
Thread t3 = new Thread(windows);
t1.setName("售票处1:");
t2.setName("售票处2:");
t3.setName("售票处3:");
t1.start();
t2.start();
t3.start();
}
}
class Windows implements Runnable {
// 不需要使用static修饰,因为t1,t2,t3都是用该类的同一对象创建的
private int ticket = 100;
Object o = new Object();
@Override
public void run() {
while (true) {
// 继承方式不能使用synchronized (this)方式同步,因为他们的this不是同一个对象
// 若要继承方式想要使用同步代码块,则可以将同步监视器声明为static
synchronized (o) { //synchronized (this) // synchronized (Windows.class)
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出号码为" + ticket + "的票");
ticket--;
} else {
break;
}
}
}
}
}
2、同步方法
private synchronized void method(){} // 同步监视器:this
// 继承方式,此时的同步监视器:类.class
private static synchronized void method(){}
- 同步方法仍然涉及到同步监视器,只是不需要显式的声明
- 非静态的同步方法,同步监视器是:this
- 静态的同步方法,同步监视器是:类.class
3、解决“懒汉式”线程不安全的问题
class Bank {
private Bank() {}
private static Bank bank = null;
public static Bank getBank() {
// 这种方式效率较高
if (bank == null) {
synchronized (Bank.class) {
if (bank == null) {
bank = new Bank();
}
}
}
return bank;
}
}
死锁
理解:不同的线程分别占用对方所需要的同步资源不放弃,都在等待对方放弃自己所需要的同步资源,就形成了线程的死锁
说明:
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
- 我们使用同步时,要避免死锁的出现
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s1) {
s1.append('a');
s2.append(1);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s1.append('b');
s2.append(2);
System.out.println(s1);
System.out.println(s2);
}
}
}
}).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);
System.out.println(s2);
}
}
}
}).start();
3、Lock锁
Lock是一个接口,ReentrantLock是它的一个实现类
private ReentrantLock lock = new ReentrantLock(); // 实例化ReentrantLock
@Override
public void run() {
try {
lock.lock(); // 调用锁定方法lock()
// 需要同步的代码
} finally {
lock.unlock(); // 调用解锁方法unlock()
}
}
使用优先顺序:Lock、同步代码块、同步方法
同步的缺点:操作同步代码时,只能一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
线程的通信
wait();//让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)
notify();//唤醒当前对象上的等待线程
notifyAll();//唤醒所有线程
- 以上三个方法都只能使用在同步代码块或同步方法中
- 以上三个方法调用者必须是同步代码块或同步方法的同步监视器
- 以上三个方法都定义在java.lang.Object类中
class Windows implements Runnable {
private int num = 1;
private Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
obj.notify();//唤醒线程
if (num <= 100) {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
try {
obj.wait();//让当前线程处于阻塞状态,释放同步锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
break;
}
}
}
}
}
JDK5.0新增的多线程创建的两种方法
1. 实现Callable接口
1.创建一个实现Callable的实现类
2.实现call(),将此线程需要执行的操作声明在call()中
3.创建Callable接口实现类的对象
4.将此Callable接口实现类的对象作为参数传递到FutureTask类的构造器中,创建FutureTask类的对象
5.将FutureTask类的对象作为参数传递到Thread类的构造器中,创建Thread类对象,并调用start()
6.获取Callable中call()的返回值(可选)
public class ThreadNew {
public static void main(String[] args) {
// 3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
// 4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
// 5.将FutureTask类的对象作为参数传递到Thread类的构造器中,创建Thread类对象,并调用start()
new Thread(futureTask).start();
// 6.获取Callable中call()的返回值(可选)
try {
// get()返回值即为FutureTask构造器参数Callable实现类重写的call()返回值
Object sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
// 1.创建一个实现Callable的实现类
class NumThread implements Callable {
@Override
// 2.实现call(),将此线程需要执行的操作声明在call()中
public Object call() throws Exception { // 求100以内偶数和
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;// 自动转换为包装类
}
}
2. 使用线程池
public class ThreadPool {
public static void main(String[] args) {
// 1、提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
// 设置线程池的属性
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
service1.setCorePoolSize(15); // 设置核心池的大小
// 2、执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread()); // 适合用于Runnable
service.execute(new NumberThread1()); // 适合用于Runnable
// service.submit(); // 适合用于Callable
// 关闭连接池
service.shutdown();
}
}
class NumberThread implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}