程序,进程,线程,并行,并发
- 程序是静态的,进程process是动态的
- 一个进程至少有一个线程
- 多线程程序优点
- 提高应用程序的响应
- 提高CPU利用率
- 改善程序结构
- 并行parallel,指两个或多个事件在同一时刻发生
- 并发concurrency,两个或多个事件在同一个时间段内发生,宏观上是多个进程同步进行
创建和启动线程
JVM允许程序运行多个线程,java.lang.thread
代表线程。所有的线程对象都必须是Thread类或其子类实例。
创建线程的方式:
- 1、继承Thread类
- 2、实现Runnable接口
创建线程方式1示例
java
public class EvenNumberTest {
public static void main(String[] args) {
PrintNumber printNumber = new PrintNumber();
printNumber.start(); // 启动线程,调用当前线程的run(),即子类重写的run()方法
}
}
class PrintNumber extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++){
if ( i % 2 == 0) System.out.println(i);
}
}
}
-
获取当前线程的名称:
Thread.currentThread().getName()
。 -
一条路径就是单线程。
-
不能用run()代替start()方法,start()要先启动线程,创建一个新的线程。
-
不能让已经start()的线程,再次执行start()操作。
创建线程方式2示例
java
public class EvenNumberTest {
public static void main(String[] args) {
EvenNumberPrint t1 = new EvenNumberPrint();
new Thread(t1).start();
}
}
class EvenNumberPrint implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++){
if (i % 2 == 0) System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
创建方式1,方式2的区别
共同点:
- 启动线程,使用的都是Thread类中定义的start()
- 创建的线程对象,都是Thread类或其子类的实例。
不同点:一个是类的继承,一个是接口的实现。
建议:建议使用实现Runnable接口的方式。
Runnable方式的好处:
① 实现的方式,避免的类的单继承的局限性
② 更适合处理有共享数据的问题。
③ 实现了代码和数据的分离。
联系:public class Thread implements Runnable (代理模式)
Thread类常用方法和生命周期
构造器
- public Thread() :分配一个新的线程对象。
- public Thread(String name) :分配一个指定名字的新的线程对象。
- public Thread(Runnable target) :指定创建线程的目标对象,它实现了Runnable接口中的run方法
- public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
public Thread(String name)示例
java
public class EvenNumberTest {
public static void main(String[] args) {
EvenNumberThread t1 = new EvenNumberThread("线程1");// 相当于给线程命名
t1.start();
}
}
class EvenNumberThread extends Thread{
public EvenNumberThread(){
}
public EvenNumberThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 100; i++){
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
线程常用方法
- start():①启动线程 ②调用线程的run()
- run():将线程要执行的操作,声明在run()中。
- currentThread():获取当前执行代码对应的线程
- getName(): 获取线程名
- setName(): 设置线程名
- sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数
- yield():静态方法,一旦执行此方法,就释放CPU的执行权
- join(): 在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行。
- isAlive():判断当前线程是否存活
线程的优先级
每个线程都有一定的优先级,同优先级线程组成先进先出队列,使用分时调度。优先级高的采用抢占式策略。
每个线程的默认优先级与创建他的父线程具有相同的优先级。
- getPriority():获取线程的优先级
- setPriority():设置线程的优先级。范围[1,10]
Thread的三个优先级常量:
- MAX_PRIORITY(10),最高优先级
- MIN_PRIORITY,最低优先级
- NORM_PRIORITY,普通优先级
生命周期
JDK1.5之前,5中状态
之后
阻塞分的更细
同步代码块解决两种线程创建方式的线程安全问题
示例及原因
同一个资源问题和线程安全问题。模拟车站售票,实现多个窗口同时售票。不能出现错票,重票。
下面的实现,是一个线程不安全的问题。两种实现线程方式都会出现重票,错票的情况。
java
public class WindowTest {
public static void main(String[] args) {
// 创建对象
SaleTicket s = new SaleTicket(); // 创建一个对象,被三个线程所共享
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
// 创建三个窗口
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
// SaleTicket1 t2 = new SaleTicket1();
// t2.start();
}
}
原因是当前进行的线程还没结束,其他线程也参与进来,对ticket进行操作。
解决办法
必须保证一个线程a在操作ticket的过程中,其它线程必须等待,直到线程a操作ticket结束以后,其它线程才可以进来继续操作ticket。
这里ticket共同操作的数据,即共享数据。
java解决方式
使用线程的同步机制。
两种方式:
- 同步代码块
- 同步方法
本质上是一样的。
同步代码块
java
synchronized(同步监视器){
//需要被同步的代码
}
说明:
- 需要被同步的代码,即为操作共享数据的代码。
- 共享数据:即多个线程都需要操作的数据。比如:ticket
- 需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其它线程必须等待。
- 同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码。
- 同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器。
使用接口runnable
在实现Runnable接口的方式中,同步监视器可以考虑使用:this
java
public class WindowTest {
public static void main(String[] args) {
// 创建对象
SaleTicket s = new SaleTicket(); // 创建一个对象,被三个线程所共享
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
// 创建三个窗口
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
// SaleTicket1 t2 = new SaleTicket1();
// t2.start();
}
}
// 接口方式
class SaleTicket implements Runnable{
Integer tickets = 100;
Object obj = new Object();
@Override
public void run() {
// System.out.println(Thread.currentThread().getName() +":卖票");
while (true){
// 增加休眠时间
try{
Thread.sleep(5);
}catch (InterruptedException e){
e.printStackTrace();
}
synchronized(obj){ // 可直接用this,也代替对象
// 这里面如果添加sleep也是锁住进程的
if (tickets > 0){
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
tickets --;
}else break;
}
}
}
}
继承Thread
如果监视器使用this,则每个线程会有一个监视器,不能保证锁的唯一性。
在继承Thread类的方式中,同步监视器要慎用this,可以考虑使用:当前类.class
java
public class WindowTest {
public static void main(String[] args) {
// 创建对象
SaleTicket1 t2 = new SaleTicket1();
SaleTicket1 t3 = new SaleTicket1();
SaleTicket1 t4 = new SaleTicket1();
t2.setName("窗口2");
t3.setName("窗口3");
t4.setName("窗口4");
t2.start();
t3.start();
t4.start();
}
}
class SaleTicket1 extends Thread {
static int tickets = 100;
static Object obj = new Object(); // 加上static,静态唯一的
@Override
public void run() {
while (true) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (obj) {// 实际上也是对象class clz = 当前类.class
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
tickets--;
} else break;
}
}
}
}
同步方法
如果操作共享数据的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。
- 非静态的同步方法,默认同步监视器是this。
- 静态的同步方法,默认同步监视器是当前类本身。
使用接口runnable
先将同步代码块中的需要被同步的代码用方法进行封装,此时还是同步代码块。
java
class SaleTicket2 implements Runnable{
Integer tickets = 100;
Object obj = new Object();
Boolean isFlag = true;
@Override
public void run() {
// System.out.println(Thread.currentThread().getName() +":卖票");
while (isFlag){
// 增加休眠时间
try{
Thread.sleep(5);
}catch (InterruptedException e){
e.printStackTrace();
}
synchronized(obj){
show();
}
}
}
public void show() {
if (tickets > 0){
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
tickets --;
}else{
isFlag = false;
}
}
}
修改为同步方法,
非静态的同步方法,默认同步监视器是this。
java
public synchronized void show() { // 非静态,监视器是this
if (tickets > 0){
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
tickets --;
}else{
isFlag = false;
}
}
注意:synchronized好处:解决了线程的安全问题。
弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能低。
java
public class WindowTest1 {
public static void main(String[] args) {
SaleTicket2 t2 = new SaleTicket2();
SaleTicket2 t3 = new SaleTicket2();
SaleTicket2 t4 = new SaleTicket2();
t2.setName("窗口2");
t3.setName("窗口3");
t4.setName("窗口4");
t2.start();
t3.start();
t4.start();
}
}
class SaleTicket2 extends Thread {
static int tickets = 100;
static Object obj = new Object(); // 加上static,静态唯一的
static boolean isFlag = true;
@Override
public void run() {
while (isFlag) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// synchronized (SaleTicket1.class) { // 实际上也是对象class clz = 当前类.class
show();
// }
}
}
public static synchronized void show(){
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
tickets--;
} else isFlag = false;
}
}